准备
为后面测试示例编写代码及配置如下:
package com.zze.bean; import java.util.Date; public class User { private String name; private Integer age; private Date birthday; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", birthday=" + birthday + '}'; } }
com.zze.bean.User
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <constant name="struts.devMode" value="true"/> <package name="test" extends="struts-default" namespace="/"> <action name="*" class="com.zze.action.{1}Action"> <result>/show.jsp</result> </action> </package> </struts>
struts.xml
<%@ page contentType="text/html;charset=UTF-8" language="java" %> v1:${requestScope.key} v2:${sessionScope.key} v3:${applicationScope.key}
show.jsp
Servlet API的访问
在使用 Struts2 的过程中,会发现 Struts2 和 Servlet 的 API 是解耦合的。在实际开发中,经常用到 Servlet 的 API,比如将信息保存到 Session 中、使用 response 响应一些内容等等..,这些都涉及到对 Servlet 的 API 的访问。
解耦合方式
package com.zze.action; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; import java.util.Arrays; import java.util.Map; public class Test1Action extends ActionSupport { /** * 解耦合方式 */ @Override public String execute() { ActionContext context = ActionContext.getContext(); // 获取请求参数 Map<String, Object> parameters = context.getParameters(); for (String key : parameters.keySet()) { String[] values = (String[]) parameters.get(key); System.out.println(String.format("key:%s value:%s", key, Arrays.toString(values))); } // 存放参数到域对象 context.put("key", "value from context.put"); // request.setAttribute("key","value from context.put") context.getSession().put("key", "value from context.getSession().put"); // session.setAttribute("key","value from context.getSession().put") context.getApplication().put("key", "value from context.getApplication().put"); // application.setAttribute("key","value from context.getApplication().put") return SUCCESS; } }
com.zze.action.Test1Action
原生Servlet的API
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; import org.apache.struts2.ServletActionContext; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.Map; public class Test2Action extends ActionSupport { @Override public String execute() throws Exception { // 获取原生 request 对象 HttpServletRequest request = ServletActionContext.getRequest(); Map<String, String[]> parameterMap = request.getParameterMap(); for (String key : parameterMap.keySet()) { String[] values = (String[]) parameterMap.get(key); System.out.println(String.format("key:%s value:%s", key, Arrays.toString(values))); } // 存放数据到域对象 request.setAttribute("key","value from request.setAttribute"); request.getSession().setAttribute("key","value from request.getSession().setAttribute"); // ServletActionContext.getServletContext() request.getServletContext().setAttribute("key","value from request.getServletContext().setAttribute"); return super.execute(); } }
com.zze.action.Test2Action
接口注入
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; import org.apache.struts2.ServletActionContext; import org.apache.struts2.interceptor.ServletRequestAware; import org.apache.struts2.interceptor.ServletResponseAware; import org.apache.struts2.util.ServletContextAware; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Arrays; import java.util.Map; /** * 接口注入的方式需要实现相应接口 */ public class Test3Action extends ActionSupport implements ServletRequestAware, ServletResponseAware, ServletContextAware { private HttpServletRequest request; // 接收注入的原生 request private HttpServletResponse response; // 接收注入的原生 response private ServletContext servletContext; // 接收注入的原生 servletContext @Override public String execute() throws Exception { // 接收请求参数 Map<String, String[]> parameterMap = request.getParameterMap(); for (String key : parameterMap.keySet()) { String[] values = (String[]) parameterMap.get(key); System.out.println(String.format("key:%s value:%s", key, Arrays.toString(values))); } // 存放数据到域对象 request.setAttribute("key", "value from request.setAttribute"); request.getSession().setAttribute("key", "value from request.getSession().setAttribute"); servletContext.setAttribute("key", "value from servletContext.setAttribute"); return super.execute(); } @Override public void setServletRequest(HttpServletRequest request) { this.request = request; } @Override public void setServletResponse(HttpServletResponse response) { this.response = response; } @Override public void setServletContext(ServletContext context) { this.servletContext = context; } }
com.zze.action.Test3Action
请求数据封装
Struts2 是一个 web 层框架,提供了请求数据封装的功能。而它的提供了如下两种方式进行请求数据的封装:
属性驱动
提供set方法
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; import java.util.Date; public class Test4Action extends ActionSupport { private String name; private Integer age; private Date birthday; public void setName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String execute() throws Exception { System.out.println(name); System.out.println(age); System.out.println(birthday); return NONE; } }
com.zze.action.Test4Action
表达式方式
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; import com.zze.bean.User; /** * 请求参数的参数名需按指定格式 */ public class Test5Action extends ActionSupport { // 给接收参数的对象提供 get 方法 private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String execute() throws Exception { System.out.println(user); return NONE; } }
com.zze.action.Test5Action : 封装到 JavaBean
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; import java.util.Arrays; import java.util.List; /** * 请求参数的参数名需按指定格式 */ public class Test6Action extends ActionSupport { public List<String> names; public List<String> getNames() { return names; } public void setNames(List<String> names) { this.names = names; } @Override public String execute() throws Exception { System.out.println(Arrays.toString(names.toArray())); return NONE; } }
com.zze.action.Test6Action : 封装到 List<String>
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; import com.zze.bean.User; import java.util.List; public class Test7Action extends ActionSupport { private List<User> users; public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; } @Override public String execute() throws Exception { for (User user : users) { System.out.println(user); } return NONE; } }
com.zze.action.Test7Action : 封装到 List<JavaBean>
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; import java.util.Arrays; import java.util.Map; public class Test8Action extends ActionSupport { private Map<String,Object> map; public Map<String, Object> getMap() { return map; } public void setMap(Map<String, Object> map) { this.map = map; } @Override public String execute() throws Exception { for (String key : map.keySet()) { String valStr= Arrays.toString((String[])map.get(key)); System.out.println(String.format("%s:%s",key,valStr)); } return NONE; } }
com.zze.action.Test8Action : 封装到 Map<String,String[]>
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; import com.zze.bean.User; import java.util.Map; public class Test9Action extends ActionSupport { private Map<String, User> map; public Map<String, User> getMap() { return map; } public void setMap(Map<String, User> map) { this.map = map; } @Override public String execute() throws Exception { for (String key : map.keySet()) { System.out.println(String.format("%s : %s", key, map.get(key))); } return NONE; } }
com.zze.action.Test9Action : 封装到 Map<String,JavaBean>
模型驱动
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; import com.zze.bean.User; /** * 实现 ModelDriven 接口 */ public class Test10Action extends ActionSupport implements ModelDriven<User> { private User user = new User(); @Override public User getModel() { return user; } @Override public String execute() throws Exception { System.out.println(user); return NONE; } }
com.zze.action.Test10Action
补充
结果页配置
全局结果页
全局结果页指的是:在包中配置一次,其它在这个包中所有 action 只要返回了对应 name 值,就都可以跳转到全局结果页配置的页面。
在 struts.xml 的 package 标签下添加如下节点即可配置全局结果页:
<!--全局结果页配置--> <global-results> <result name="success">/success.jsp</result> </global-results>
局部结果页
局部结果页指的是:所配置的结果只对当前所在 action 有效。
在 struts.xml 的 package>aciton 下配置:
<!--局部结果页配置--> <action name="*" class="com.zze.action.{1}Action"> <result>/show.jsp</result> </action>
result标签配置
result 标签用于配置页面的跳转。
属性:
- name:逻辑视图名称,与 action 返回值对应。
- type:页面跳转类型。
type 的取值有很多,在 struts2-core-2.3.37.jar!/struts-default.xml 的 package 下有定义:
<result-types> <result-type name="chain" class="com.opensymphony.xwork2.ActionChainResult"/> <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" default="true"/> <result-type name="freemarker" class="org.apache.struts2.views.freemarker.FreemarkerResult"/> <result-type name="httpheader" class="org.apache.struts2.dispatcher.HttpHeaderResult"/> <result-type name="redirect" class="org.apache.struts2.dispatcher.ServletRedirectResult"/> <result-type name="redirectAction" class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/> <result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/> <result-type name="velocity" class="org.apache.struts2.dispatcher.VelocityResult"/> <result-type name="xslt" class="org.apache.struts2.views.xslt.XSLTResult"/> <result-type name="plainText" class="org.apache.struts2.dispatcher.PlainTextResult" /> <result-type name="postback" class="org.apache.struts2.dispatcher.PostbackResult" /> </result-types>
struts-default.xml > package > result-types
常用类型有如下几个:
dispatcher:默认值,请求转发,从 当前 action 到 jsp。
redirect:从当前 action 重定向到 jsp。
chain:请求转发,从当前 action 转发到另一个 action。
redirectAction:从当前 action 重定向到另一个 action。
stream:返回流,文件下载。
INPUT逻辑视图
在 Action 接口中提供了 5 个静态字段,如下:
public static final String SUCCESS = "success"; // 操作成功 public static final String NONE = "none"; // 操作成功不跳转 public static final String ERROR = "error"; // 操作失败 public static final String INPUT = "input"; // 通常在表单提交请求参数验证失败时返回,标识需重新输入提交 public static final String LOGIN = "login"; // 返回到登录页
"input" 返回值在 Struts2 内置拦截器中就有用到,先看如下情况:
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; public class Test11Action extends ActionSupport { private Integer age; public void setAge(Integer age) { this.age = age; } @Override public String execute() throws Exception { System.out.println(age); return NONE; } }
com.zze.action.Test11Action
在上述 action 中使用属性驱动的方式接受一个 Integer 类型的年龄字段,而参数提交时提交的是 "aa","aa" 明显是不可以转成 Integer 类型的,此时 Struts2 就会返回错误如下:
No result defined for action com.zze.action.Test11Action and result input - action - file:/WEB-INF/classes/struts.xml:13:51
即:没有为 action 定义一个 name="input" 的 result 。为什么 Struts2 会返回这个结果呢?
首先我们知道 Struts2 内部默认是有一个拦截器栈的,在 struts2-core-2.3.37.jar!/struts-default.xml 中可以看到,如下:
<interceptor-stack name="defaultStack"> <interceptor-ref name="exception"/> <interceptor-ref name="alias"/> <interceptor-ref name="servletConfig"/> <interceptor-ref name="i18n"/> <interceptor-ref name="prepare"/> <interceptor-ref name="chain"/> <interceptor-ref name="scopedModelDriven"/> <interceptor-ref name="modelDriven"/> <interceptor-ref name="fileUpload"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="datetime"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="staticParams"/> <interceptor-ref name="actionMappingParams"/> <!--接收参数--> <interceptor-ref name="params"/> <!--类型转换--> <interceptor-ref name="conversionError"/> <!--数据校验--> <interceptor-ref name="validation"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="workflow"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="debugging"/> <interceptor-ref name="deprecation"/> </interceptor-stack>
在请求 action 时该栈下的拦截器会依次执行,而每个拦截器都有它自身的功能。如 params 拦截器用来接收参数、conversionError 拦截器用来类型转换、validation 拦截器用来数据校验。
而 Struts2 有一个专门存放错误的区域,当这几个操作出错时,Struts2 就会将对应错误信息存到该错误区域。接着在 workflow 拦截器中就会检查错误区域是否有错误,如果有,则返回 "input" 逻辑视图,如果没有则继续执行后续拦截器及 action。workflow 对应拦截器部分源码如下:
@Override protected String doIntercept(ActionInvocation invocation) throws Exception { Object action = invocation.getAction(); if (action instanceof ValidationAware) { ValidationAware validationAwareAction = (ValidationAware) action; // 如果错误区有错误 if (validationAwareAction.hasErrors()) { if (LOG.isDebugEnabled()) { LOG.debug("Errors on action [#0], returning result name [#1]", validationAwareAction, inputResultName); } String resultName = inputResultName; // "input" resultName = processValidationWorkflowAware(action, resultName); resultName = processInputConfig(action, invocation.getProxy().getMethod(), resultName); resultName = processValidationErrorAware(action, resultName); return resultName; } } return invocation.invoke(); }
com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor#doIntercept
了解了拦截器返回 "input" 逻辑视图的原因,我们就可以对症下药了,在 struts.xml 中 action 标签下添加一个 "input" 的结果:
<result name="input">/input.jsp</result>
而在 jsp 中我们可以通过 Struts2 提供的标签输出错误信息:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Title</title> </head> <body> <s:fielderror/> </body> </html>
input.jsp
再次请求,结果如下: