Java
Java Web框架

Struts2(4)

简介:Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个Servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts2是Struts的下一代产品,是在Struts1和WebWork的技术基础上进行了合并的全新的Struts2框架。

1. OGNL表达式

1.2. 什么是OGNL语言

OGNL的全称为Object Graphic Navigation Language(对象图导航语言)。它是Struts2的默认表达式语言!使用OGNL需要导入OGNL的Jar包:ognl-3.0.5.jar及几个依赖包。

OGNL的功能介绍:

  • EL一样的JavaBean导航;
  • 调用对象方法;
  • 调用类的静态方法;
  • 索引数组元素;
  • 操作集合;

1.3. OGNL表达式功能演示

使用OGNL取值的时候,常见的是使用其getValue方法,这个方法的三个参数的意义如下:

  • 参数1:填写ognl表达式;
  • 参数2:Map => context 上下文;
  • 参数3:JavaBean / List / Map..... Root 根;

先准备一个JavaBean,其代码如下:

  • public class User {
  • private String name;
  • private String password;
  • private int age ;
  • public int getAge() {
  • return age;
  • }
  • public void setAge(int age) {
  • this.age = age;
  • }
  • public String getName() {
  • return name;
  • }
  • public void setName(String name) {
  • this.name = name;
  • }
  • public String getPassword() {
  • return password;
  • }
  • public void setPassword(String password) {
  • this.password = password;
  • }
  • @Override
  • public String toString() {
  • return "User [name=" + name + ", password=" + password + ", address="+ address + "]";
  • }
  • }
  1. 取值
  • 根中取

  • 根是Javabean

  • @Test
  • public void ognlTest1() throws Exception{
  • User u = new User();
  • u.setName("tom");
  • String name = (String) Ognl.getValue("name", new HashMap(), u);
  • System.out.println(name);
  • }

打印信息为:

  • tom
  • 根是list([n]语法)
  • @Test
  • public void ognlTest2() throws Exception{
  • List<User> list = new ArrayList<User>();
  • User u1 = new User();
  • u1.setName("tom");
  • list.add(u1);
  • User u2 = new User();
  • u2.setName("jerry");
  • list.add(u2);
  • // ognl表达式默认从根下取数据
  • String name = (String) Ognl.getValue("[1].name", new HashMap(), list);
  • System.out.println(name);
  • }

打印信息为:

  • jerry
  • Map中取
  • @Test
  • public void ognlTest3() throws Exception{
  • Map< String, Object> context = new HashMap<String, Object>();
  • context.put("name", "tom");
  • String name = (String) Ognl.getValue("#name", context, u2);
  • System.out.println(name);
  • }

打印信息为:

  • tom
  • 逐级取值:

我们再为User类添加一个对象类型(Address)的属性address,该属性的类定义如下:

  • public class Address {
  • private String city;
  • private String street;
  • public String getCity() {
  • return city;
  • }
  • public void setCity(String city) {
  • this.city = city;
  • }
  • public String getStreet() {
  • return street;
  • }
  • public void setStreet(String street) {
  • this.street = street;
  • }
  • }

如果我们需要取到address属性的city属性,可以使用以下方式:

  • @Test
  • public void ognlTest4() throws Exception{
  • User u = new User();
  • u.setName("jerry");
  • Address a = new Address();
  • a.setCity("北京");
  • u.setAddress(a);
  • String city = (String) Ognl.getValue("address.city", new HashMap(), u);
  • System.out.println(city);
  • }

打印信息为:

  • 北京
  1. 赋值
  • 表达式赋值
  • @Test
  • public void ognlTest5() throws Exception{
  • User u = new User();
  • Ognl.getValue("name='tom'", new HashMap(), u);
  • System.out.println(u.getName());
  • }

打印信息为:

  • tom
  • SetValue方法赋值
  • @Test
  • public void ognlTest6() throws Exception {
  • User u = new User();
  • Ognl.setValue("name", new HashMap(), u, "jerry");
  • System.out.println(u.getName());
  • }

打印信息为:

  • jerry
  1. 调用方法
  • @Test
  • public void ognlTest7() throws Exception {
  • // 演示方法调用(方法需要存在于根对象中)
  • User u = new User();
  • Ognl.getValue("setName('jack')", new HashMap(), u);
  • System.out.println(u.getName());
  • }

打印信息为:

  • jack
  1. 调用静态方法

创建一个静态的工具类代码如下:

  • import java.text.SimpleDateFormat;
  • import java.util.Date;
  • public class DateUtils {
  • public static double PI = 3.14159265357;
  • public static String getTime(){
  • return new SimpleDateFormat("yyyy/MM/dd").format(new Date());
  • }
  • public static String echo(String str){
  • return str;
  • }
  • }

然后使用OGNL表达式调用该类的静态方法:

  • @Test
  • public void ognlTest8() throws Exception {
  • // 演示静态方法调用(不受方法必须在根中的限制)
  • User u = new User();
  • String time = (String) Ognl.getValue(
  • "@cn.itheima.utils.DateUtils@getTime()", new HashMap(), u);
  • String echo = (String) Ognl.getValue(
  • "@cn.itheima.utils.DateUtils@echo('echo function')", new HashMap(),
  • u);
  • System.out.println(time);
  • System.out.println(echo);
  • }

打印信息为:

  • 2017/03/19
  • echo function
  1. 访问静态变量
  • @Test
  • public void ognlTest9() throws Exception {
  • // 演示静态方法调用(不受方法必须在根中的限制)
  • User u = new User();
  • double Pi = (Double) Ognl.getValue("@cn.itheima.utils.DateUtils@PI",
  • new HashMap(), u);
  • System.out.println(Pi);
  • }

打印信息为:

  • 3.14159265357
  1. 数学运算符
  • @Test
  • public void ognlTest10() throws Exception {
  • // 演示数学运算符
  • User u = new User();
  • int result = (Integer) Ognl.getValue("1+1", new HashMap(), u);
  • System.out.println(result);
  • }

打印信息为:

  • 2
  1. ,号连接
  • @Test
  • public void ognlTest11() throws Exception {
  • // 演示","连接符
  • User u = new User();
  • String name = (String) Ognl.getValue("name='tom',name", new HashMap(),
  • u);
  • System.out.println(name);
  • }

打印信息为:

  • tom
  1. 创建List
  • @Test
  • public void ognlTest12() throws Exception {
  • // 演示 创建对象 (list)
  • User u = new User();
  • List list = (List) Ognl.getValue("{'tom','jerry','jack','rose'}",
  • new HashMap(), u);
  • System.out.println(list);
  • }

打印信息为:

  • [tom, jerry, jack, rose]
  1. 创建Map
  • @Test
  • public void ognlTest13() throws Exception {
  • // 演示 创建对象 (map)
  • User u = new User();
  • Map map = (Map) Ognl.getValue("#{'name':'tom','age':'18'}",
  • new HashMap(), u);
  • System.out.println(map);
  • }

打印信息为:

  • {name=tom, age=18}
  1. 创建对象
  • @Test
  • public void ognlTest14() throws Exception {
  • // 演示 创建对象 (user)
  • User u = new User();
  • User u2 = (User) Ognl.getValue("new cn.itheima.bean.User()",
  • new HashMap(), u);
  • System.out.println(u2);
  • }

打印信息为:

  • User [name=null, password=null, address=null]
  1. innot in运算符
  • @Test
  • public void ognlTest15() throws Exception {
  • // 演示 in
  • User u = new User();
  • boolean b = (Boolean) Ognl.getValue(
  • "'tom' in {'tom','jerry','jack','rose'}", new HashMap(), u);
  • boolean c = (Boolean) Ognl.getValue(
  • "'tom' not in {'tom','jerry','jack','rose'}", new HashMap(), u);
  • System.out.println(b);
  • System.out.println(c);
  • }

打印信息为:

  • true
  • false
  1. 投影(了解)
  • @Test
  • public void ognlTest16() throws Exception {
  • // 集合的投影(了解)
  • List<User> list = new ArrayList<User>();
  • User u1 = new User();
  • u1.setName("tom");
  • list.add(u1);
  • User u2 = new User();
  • u2.setName("jerry");
  • list.add(u2);
  • System.out.println(Ognl.getValue("#this.{name}", new HashMap(), list));
  • }

打印信息为:

  • [tom, jerry]
  1. 过滤(了解)
  • @Test
  • public void ognlTest17() throws Exception {
  • // 集合的投影(了解)
  • List<User> list = new ArrayList<User>();
  • User u1 = new User();
  • u1.setName("tom");
  • u1.setAge(10);
  • list.add(u1);
  • User u2 = new User();
  • u2.setName("jerry");
  • u2.setAge(20);
  • list.add(u2);
  • System.out.println(Ognl.getValue("#this.{?age > 18}", new HashMap(),
  • list));
  • }

打印信息为:

  • [User [name=jerry, password=null, address=null]]

1.4. OGNL与Struts2的结合

在Struts中,维护了一个ValueStack用于存储某些值,OGNL会在某些取值和赋值操作中默认操作ValueStack的栈顶元素。我们可以通过以下的例子来验证。

首先我们创建一个JavaBean对象,代码如下:

  • package com.coderap.valuestack;
  • public class User {
  • private String name;
  • public String getName() {
  • return name;
  • }
  • public void setName(String name) {
  • this.name = name;
  • }
  • @Override
  • public String toString() {
  • return "User [name=" + name + "]";
  • }
  • }

上面的JavaBean对象只有一个属性name。接下来我们创建一个Action,代码如下:

  • package com.coderap.valuestack;
  • import com.opensymphony.xwork2.ActionContext;
  • import com.opensymphony.xwork2.ActionSupport;
  • import com.opensymphony.xwork2.util.ValueStack;
  • public class Demo1Action extends ActionSupport {
  • private String name;
  • public String getName() {
  • return name;
  • }
  • public void setName(String name) {
  • this.name = name;
  • }
  • public String execute() {
  • System.out.println("action中的name属性值:" + name);
  • return "success";
  • }
  • }

在该Action中,同样拥有一个属性name。另外我们需要配置一个拦截器,在这个Action执行之前做一些操作:

  • package com.coderap.valuestack;
  • import com.opensymphony.xwork2.ActionContext;
  • import com.opensymphony.xwork2.ActionInvocation;
  • import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
  • import com.opensymphony.xwork2.util.ValueStack;
  • public class ValueStackInterceptor extends AbstractInterceptor {
  • @Override
  • public String intercept(ActionInvocation invocation) throws Exception {
  • // 1 得到ActionContext数据中心
  • ActionContext ac = ActionContext.getContext();
  • // 2 获得ValueStack(List)
  • ValueStack vs = ac.getValueStack();
  • // 3 取出栈中的第一个对象验证是否是Action
  • Object obj = vs.getRoot().peek();
  • System.out.println("栈顶的对象是:" + obj);// 应该是Demo1Action
  • // 4 创建一个User对象压入栈中
  • User u = new User();
  • vs.getRoot().push(u);// 现在User对象替代Action在栈顶了
  • // 放行
  • return invocation.invoke();
  • }
  • }

这个拦截器的代码首先将会取出ValueStack的栈顶元素,默认情况下,栈顶元素将会是当前的Action,上面的代码也进行了验证;然后拦截器会创建一个User对象并将其压入ValueStack栈内;最后做放行操作。

在XML文件中配置相应的Action和拦截器:

  • <interceptors>
  • <interceptor name="valuestack" class="com.coderap.valuestack.ValueStackInterceptor"></interceptor>
  • </interceptors>
  • <action name="Demo1Action" class="com.coderap.valuestack.Demo1Action" >
  • <result name="success" type="dispatcher" >/index.jsp</result>
  • <interceptor-ref name="valuestack"></interceptor-ref>
  • <interceptor-ref name="defaultStack"></interceptor-ref>
  • </action>

在浏览器访问以下地址:http://liuyd:8080/struts2-demo3/valuestack/Demo1Action.do?name=tom

理论上我们会将tom值设置到Demo1Action中的name属性中去,但是由于拦截器的效果会将栈顶的对象替换为一个User对象,而将tom值设置到User对象中的name属性中去,得到的打印信息如下:

  • 栈顶的对象是:com.coderap.Demo1Action@17a73d30
  • action中的name属性值:null

1.4.1. 对象导航赋值

由于OGNL表达式有为属性赋值的操作,所以可以通过特殊的请求方式将数据注入到Action的某个属性中。创建一个新的Action代码如下:

  • import com.opensymphony.xwork2.ActionContext;
  • import com.opensymphony.xwork2.ActionSupport;
  • import com.opensymphony.xwork2.util.ValueStack;
  • public class Demo2Action extends ActionSupport {
  • private User user;
  • public String execute() {
  • // http://localhost:8080/struts2-demo3/valuestack/Demo2Action.do?user.name=tom
  • System.out.println(user);
  • return "success";
  • }
  • public User getUser() {
  • return user;
  • }
  • public void setUser(User user) {
  • this.user = user;
  • }
  • }

对于以上的Action,我们可以通过访问http://localhost:8080/struts2-demo3/valuestack/Demo2Action.do?user.name=tomtom值设置到Action的User对象中的name属性中去,打印信息如下:

  • User [name=tom]

1.4.2. ModelDriven的方式

如果我们的Action继承了ModelDriven接口,并声明了相应的对象类型泛型,就可以直接通过http://localhost:8080/struts2-demo3/valuestack/Demo3Action.do?name=tomtom值设置到Action的User对象中的name属性中去,这也是由于ValueStack的缘由:

  • package com.coderap.valuestack;
  • import com.opensymphony.xwork2.ActionContext;
  • import com.opensymphony.xwork2.ActionSupport;
  • import com.opensymphony.xwork2.ModelDriven;
  • import com.opensymphony.xwork2.util.ValueStack;
  • public class Demo3Action extends ActionSupport implements ModelDriven<User> {
  • private User user = new User();
  • public String execute() {
  • // http://localhost:8080/struts2-demo3/valuestack/Demo3Action.do?name=tom
  • System.out.println(user);
  • return "success";
  • }
  • public User getUser() {
  • return user;
  • }
  • public void setUser(User user) {
  • this.user = user;
  • }
  • public User getModel() {
  • return user;
  • }
  • }

这是由于struts默认配置的拦截器中,<interceptor name="modelDriven" class="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"/>是处于<interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/>之前的,在赋值之前,就需要经过modelDriven拦截器进行OGNL的属性赋值操作。对于modelDriven的类com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor源代码如下:

  • @Override
  • public String intercept(ActionInvocation invocation) throws Exception {
  • Object action = invocation.getAction();
  • if (action instanceof ModelDriven) {
  • ModelDriven modelDriven = (ModelDriven) action;
  • ValueStack stack = invocation.getStack();
  • Object model = modelDriven.getModel();
  • if (model != null) {
  • stack.push(model);
  • }
  • if (refreshModelBeforeResult) {
  • invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
  • }
  • }
  • return invocation.invoke();
  • }

可以发现,这段代码会首先判断当前的action是否实现了ModelDriven接口,如果实现了这个接口,就调用其getModel()方法获取model对象,然后将该对象压入ValueStack栈中,所以我们可以通过http://liuyd:8080/struts2-demo3/valuestack/Demo3Action.do?name=tom直接tom值设置到Action的User对象中的name属性中去。

struts2为OGNL表达式准备了两个对象:ActionContext作为OGNL表达式的ContextValueStack作为OGNL表达式的Root,这两个对象的创建都是在StrutsPrepareAndExecuteFilter中准备好的。

总结:表单提交时,其中提交的键可以看作是ognl表达式;Action中有User对象,我们想直接将表单参数提交到User对象中封装,有做法:

  1. 提交的参数的键为user.name时,就会在值栈中查找名为user的对象,并赋值到该对象的name属性。

  2. 使用ModelDriven时,我们的ActiongetModel()方法中将user对象返回,ModelDriven拦截器会将我们返回的user对象放入值栈的栈顶,那么在表单中直接提交name时将会把name值装入栈顶的user对象的name属性。

1.5. 文件下载中文件名乱码问题解决

在使用Struts进行文件下载的时候,如果下载保存的文件名为中文,就会出现乱码的情况,这个时候可以使用OGNL表达式来封装文件名并进行URLEncoder操作,防止下载的文件名乱码;相应的Action的<result>标签配置文件如下:

  • <result name="success" type="stream" >
  • <param name="contentType">application/zip</param>
  • <param name="inputName">is</param>
  • <!--
  • 1. 相应头中只能使用latin(Iso-8859-1)码表
  • 2. 使用URLEncode编码,对中文进行编码才能发送 => %E6%F7
  • 3. 在struts.xml配置文件中可以使用ognl表达式 => ${} 在扩种书写ognl(注意:虽然跟el表达式格式看似相同,但是就是ognl不是el)
  • -->
  • <param name="contentDisposition">attachment;filename="${fileName}"</param>
  • <param name="bufferSize">10240</param>
  • </result>

这些配置中,可以使用attachment;filename="${fileName}"中的${fileName}从Action中取出文件名配置,因此还需要在Action中返回相应的文件名:

  • package com.coderap.ognl;
  • import java.io.InputStream;
  • import java.io.UnsupportedEncodingException;
  • import java.net.URLEncoder;
  • import javax.servlet.ServletContext;
  • import org.apache.struts2.ServletActionContext;
  • import com.opensymphony.xwork2.ActionContext;
  • import com.opensymphony.xwork2.ActionSupport;
  • public class Demo1Action extends ActionSupport {
  • private InputStream is;
  • public InputStream getIs() {
  • // 1 获得ServletContext
  • ServletContext sc = ServletActionContext.getServletContext();
  • // 2 获得apache-tomcat-6.0.35.zip的流
  • is = sc.getResourceAsStream("/WEB-INF/apache-tomcat-6.0.35.zip");
  • // 3 返回
  • return is;
  • }
  • public String getFileName() {
  • try {
  • return URLEncoder.encode("文件下載.zip", "UTF-8");
  • } catch (UnsupportedEncodingException e) {
  • e.printStackTrace();
  • return null;
  • }
  • }
  • @Override
  • public String execute() throws Exception {
  • return SUCCESS;
  • }
  • }