Struts2 OGNL

时间:2020-02-23 14:36:06  来源:igfitidea点击:

Struts2 OGNL是表达语言,其中OGNL代表对象图导航语言。
OGNL与Struts2紧密耦合,用于将表单参数作为Java bean变量存储在ValueStack中,并在结果页面中从ValueStack检索值。

Struts2 OGNL

Struts2 OGNL执行两项重要任务-数据传输和类型转换。

Struts2中的OGNL从servlet请求中获取请求参数,并将其传输到相应的java变量。

由于我们以String形式获取请求参数,而Java Bean变量可以是String,int,数组,列表或者任何自定义对象,因此类型转换也是一项重要任务,OGNL会通过其内置的类型转换器来处理类型转换。

Struts2 OGNL非常灵活,我们可以轻松地对其进行扩展以创建我们自己的自定义转换器类。
我们将首先使用基本数据类型(例如String,boolean,int,数组和列表)来研究OGNL的用法。
然后,我们将为自定义java bean变量创建自己的转换器类。

Struts2 OGNL示例

我们针对Struts2 OGNL示例的最终项目结构如下图所示。

Struts 2配置文件

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xmlns="https://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	id="WebApp_ID" version="3.0">
	<display-name>Struts2OGNLExample</display-name>

	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>

pom.xml

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>Struts2OGNLExample</groupId>
	<artifactId>Struts2OGNLExample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<dependencies>
		<dependency>
			<groupId>org.apache.struts</groupId>
			<artifactId>struts2-core</artifactId>
			<version>2.3.15.1</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.3</version>
				<configuration>
					<warSourceDirectory>WebContent</warSourceDirectory>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
		</plugins>
		<finalName>${project.artifactId}</finalName>
	</build>
</project>

struts.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"https://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<constant name="struts.devMode" value="false"></constant>
<constant name="struts.convention.result.path" value="/"></constant>
<package name="user" namespace="/" extends="struts-default">
	<action name="home">
		<result>/home.jsp</result>
	</action>
	<action name="welcome" class="com.theitroad.struts2.actions.WelcomeAction">
	<result name="success">/welcome.jsp</result>
	</action>

</package>

</struts>

配置文件是易于理解的,它们只是为了配置我们的应用程序以使用Struts 2框架。

Struts2 OGNL示例模型类

package com.theitroad.struts2.model;

public class Data {

	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	
}
package com.theitroad.struts2.model;

public class Rectangle {

	private int x;
	private int y;
	
	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
	
	
}
package com.theitroad.struts2.model;

import java.util.Date;
import java.util.List;
import java.util.Map;

public class MyJavaBean {

	private String name;
	private boolean flag;
	private Integer age;
	private Date date;
	private String[] stocks;
	//roles array needs to initialize because it's used with index in form
	private String[] roles = new String[5];
	
	//do not preinitialize lists or any collections
	private List<Data> usersList;
	private List<Data> fruitsList;
	private Map<String, Data> usersMap;
	
	//custom type converter example
	private Rectangle rectangle;
	
	public List<Data> getFruitsList() {
		return fruitsList;
	}
	public void setFruitsList(List<Data> fruitsList) {
		this.fruitsList = fruitsList;
	}
	public List<Data> getUsersList() {
		return usersList;
	}
	public void setUsersList(List<Data> users) {
		this.usersList = users;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public boolean isFlag() {
		return flag;
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public Date getDate() {
		return date;
	}
	public void setDate(Date date) {
		this.date = date;
	}
	public String[] getStocks() {
		return stocks;
	}
	public void setStocks(String[] stocks) {
		this.stocks = stocks;
	}
	public String[] getRoles() {
		return roles;
	}
	public void setRoles(String[] roles) {
		this.roles = roles;
	}
	public Map<String, Data> getUsersMap() {
		return usersMap;
	}
	public void setUsersMap(Map<String, Data> usersMap) {
		this.usersMap = usersMap;
	}
	public Rectangle getRectangle() {
		return rectangle;
	}
	public void setRectangle(Rectangle rectangle) {
		this.rectangle = rectangle;
	}
	
}

MyJavaBean是我们将要使用的动作Bean类,请注意类型为Rectangle的变量。
由于这是一个自定义类,因此我们需要为此实现自己的转换器类。
稍后我们将对此进行调查。

Struts2 OGNL示例动作类

package com.theitroad.struts2.actions;

import com.theitroad.struts2.model.MyJavaBean;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

public class WelcomeAction extends ActionSupport implements ModelDriven<MyJavaBean>{

	public String execute(){
		return SUCCESS;
	}
	
	private MyJavaBean bean = new MyJavaBean();
	
	@Override
	public MyJavaBean getModel() {
		return bean;
	}

}

动作类仅返回成功页面,此处没有逻辑。

Struts2 OGNL示例结果页面

home.jsp

<%@ page language="java" contentType="text/html; charset=US-ASCII"
  pageEncoding="US-ASCII"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Home Page Form</title>
</head>
<body>
<h3>Struts 2 OGNL Examples</h3>
<s:form action="welcome" method="post">
<table>
<s:textfield name="name" label="Name" ></s:textfield>
<s:textfield name="flag" label="True/False?"></s:textfield>
<s:textfield name="age" label="Current Year?"></s:textfield>
<s:textfield name="date" label="Todays Date (mm/dd/yyyy)?"></s:textfield>
</table>
<h3>Struts 2 Array OGNL Example</h3>

Array with same name example<br>
<table>
<s:textfield name="stocks" label="Stock 1"></s:textfield>
<s:textfield name="stocks" label="Stock 2"></s:textfield>
<s:textfield name="stocks" label="Stock 3"></s:textfield>
</table>

Array with indexed name example<br>
<table>
<s:textfield name="roles[0]" label="Role 1"></s:textfield>
<s:textfield name="roles[1]" label="Role 2"></s:textfield>
<s:textfield name="roles[2]" label="Role 3"></s:textfield>
</table>

<h3>Struts 2 List OGNL Example</h3>

List with same name example<br>
<table>
<s:textfield name="usersList.name" label="User 1 Name"></s:textfield>
<s:textfield name="usersList.name" label="User 2 Name"></s:textfield>
<s:textfield name="usersList.name" label="User 3 Name"></s:textfield>
</table>
List with indexed name example<br>
<table>
<s:textfield name="fruitsList[0].name" label="Fruit 1"></s:textfield>
<s:textfield name="fruitsList[1].name" label="Fruit 2"></s:textfield>
<s:textfield name="fruitsList[2].name" label="Fruit 3"></s:textfield>
</table>

Map Example
<table>
<s:textfield name="usersMap['first'].name" label="User 1"></s:textfield>
<s:textfield name="usersMap['second'].name" label="User 2"></s:textfield>
<s:textfield name="usersMap['third'].name" label="User 3"></s:textfield>
</table>

Custom Converter Example
<table>
<s:textfield name="rectangle" label="Rectangle in format R:x,y"></s:textfield>
</table>
<s:submit label="Submit" align="left"></s:submit>
</s:form>
</body>
</html>

home.jsp用作输入页面,用户可以其中提供值并调用欢迎操作。

welcome.jsp

<%@ page language="java" contentType="text/html; charset=US-ASCII"
  pageEncoding="US-ASCII"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Welcome Page Example</title>
</head>
<body>
<h3>Struts 2 OGNL Examples</h3>
Name = <s:property value="name"></s:property><br>
True/False? = <s:property value="flag" ></s:property><br>
Current Year? = <s:property value="age"></s:property><br>
Todays Date (mm/dd/yyyy)? = <s:date name="date" format="MM/dd/yyyy"></s:date><br>

Stocks Array = <s:property value="stocks"<br>
Roles Array = <s:property value="roles"<br>
Users List = <s:iterator value="usersList"><s:property value="name"  </s:iterator><br>
Fruits List = <s:iterator value="fruitsList"><s:property value="name"  </s:iterator><br>
Fruit 1 Name = <s:property value="fruitsList[0].name"<br>

Users Map = <s:iterator value="usersMap">{<s:property value="key",<s:property value="value.name"}  </s:iterator><br>

Rectangle Dimensions: x = <s:property value="rectangle.x" and y = <s:property value="rectangle.y"<br>
</body>
</html>

welcome.jsp仅用于显示用户使用的值,以证明OGNL正在处理数据传输和类型转换。

在继续讨论自定义转换器类之前,让我们看一下上述实现中的一些要点。

  • 基本数据类型的转换是自动的,我们不需要遵循任何特殊的规则。

  • Struts 2还负责将String转换为Date,我们可以使用s:date以特定格式显示它。

  • 数组和列表可以与名称和索引一起使用。
    如果使用索引,则需要在bean中初始化数组。
    因此,在bean类中初始化了角色数组。

  • 不要在bean中初始化列表变量,否则会抛出错误。
    OGNL负责初始化和填充值。

  • Struts2中的OGNL还提供了对Map的内置支持,我们可以在结果页中使用它。

  • 我们可以将迭代器与List,Map,Array等多值数据类型一起使用来遍历它们。
    我们可以使用索引或者键从这些变量中获取特定值。

Struts2 OGNL自定义类型转换器

创建和配置自定义类型转换器类非常容易。
第一步是修复自定义类的输入格式。
在我的示例中,我将用户输入固定为" R:x,y",其中x和y是矩形变量,应为整数。

第二步是实现转换器类。
类型转换器类应实现com.opensymphony.xwork2.conversion.TypeConverter接口。

由于在Web应用程序中,我们总是以String的形式获取请求,并以String的形式发送响应,因此Struts 2 API提供了TypeConverter接口的默认实现,即StrutsTypeConverter。

StrutsTypeConverter包含两个抽象方法– convertFromString将String转换为Object,convertToString将Object转换为String。
我们将为自定义类型转换器扩展此类。

package com.theitroad.struts2.typeconverters;

import java.util.Map;

import org.apache.struts2.util.StrutsTypeConverter;

import com.theitroad.struts2.model.Rectangle;
import com.opensymphony.xwork2.conversion.TypeConversionException;

/**
 * Custom type converter to convert user input to Rectangle
 * Format is R:x,y where x and y are int defining Rectangle dimensions 
 * @author hyman
 *
 */
public class RectangleTypeConverter extends StrutsTypeConverter {

	@Override
	public Object convertFromString(Map arg0, String[] inputs, Class arg2) {
		String input = inputs[0];
		if(!input.startsWith("R:")) throw new TypeConversionException("invalid input");
		input = input.substring(2);
		String[] dimensions = input.split(",");
		int x = Integer.parseInt(dimensions[0]);
		int y = Integer.parseInt(dimensions[1]);
		Rectangle rect = new Rectangle();
		rect.setX(x);
		rect.setY(y);
		return rect;
	}

	@Override
	public String convertToString(Map arg0, Object obj) {
		Rectangle rect = (Rectangle) obj;
		String output = "R:" + rect.getX() + "," + rect.getY();
		return output;
	}

}

请注意,代码非常简单,可以将输入字符串解析为对象,反之亦然。

下一步是配置要用于Rectangle类型变量的类型转换器。
有两种配置方式–第一种是针对特定操作进行配置,第二种方式是进行全局配置。

对于特定于动作的转换器,我们可以使用com.opensymphony.xwork2.conversion.annotations.TypeConversion注释,并如下更改setter方法。

@TypeConversion(converter="com.theitroad.struts2.typeconverters.RectangleTypeConverter")
public void setRectangle(Rectangle rectangle) {
	this.rectangle = rectangle;
}

用于ModelDriven动作类的自定义类型转换器

如果Action类正在为Java bean实现ModelDriven接口,则另一种方法是创建名称为{JavaBeanName} -conversion.properties的属性文件,并将其放入与Java bean类相同的包中,因此我们可以创建MyJavaBean-conversion.properties和将其放入带有以下数据的com.theitroad.struts2.model包中。

MyJavaBean-conversion.properties

#For Action Classes implementing ModelDriven<MyJavaBean>
#variable-name=TypeConverter class name
rectangle=com.theitroad.struts2.typeconverters.RectangleTypeConverter

如我在本项目中所做的那样,要进行全局转换,我们需要创建xwork-conversion.properties属性文件,并确保它位于WEB-INF/classes目录中。
我们需要提供类名和转换器作为键值对。
对我们来说

xwork-conversion.properties

#Application level custom converter configuration
com.theitroad.struts2.model.Rectangle=com.theitroad.struts2.typeconverters.RectangleTypeConverter