struts2基础
注意:
1.action对象是多例的,每次请求都创建一个对象,线程安全
2.struts2默认拦截没有后缀,或者.action的请求
3.struts2注解依赖的jar包:struts2-convention-plugin-2.3.15.3.jar,asm...jar[三个]
4.jar包:
1.struts2的配置文件
default.properties:注意配置常量 【不可改,在jar包中】
struts-default.xml :常量,bean,struts-default包【结果视图,拦截器】,默认动作类,动作方法【不可改,jar包中】
struts-plugin.xml 【不可改】
struts.xml 【自己创建】
struts.properties 【自己创建】
web.xml
加载顺序:default.propeties ->struts-default.xml->struts-plugin.xml->struts.properties->web.xml
2.struts2核心思想
结果视图:
以ServletDispatcherResult为例:继承StrutsResultSupport类,
public class ServletDispatcherResult extends StrutsResultSupport { private static final long serialVersionUID = -1970659272360685627L; private static final Logger LOG = LoggerFactory.getLogger(ServletDispatcherResult.class); private UrlHelper urlHelper; public ServletDispatcherResult() { } public ServletDispatcherResult(String location) { super(location); } @Inject public void setUrlHelper(UrlHelper urlHelper) { this.urlHelper = urlHelper; } public void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Forwarding to location " + finalLocation, new String[0]); } PageContext pageContext = ServletActionContext.getPageContext(); if (pageContext != null) { pageContext.include(finalLocation); } else { HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation); if (StringUtils.isNotEmpty(finalLocation) && finalLocation.indexOf("?") > 0) { String queryString = finalLocation.substring(finalLocation.indexOf("?") + 1); Map<String, Object> parameters = this.getParameters(invocation); Map<String, Object> queryParams = this.urlHelper.parseQueryString(queryString, true); if (queryParams != null && !queryParams.isEmpty()) { parameters.putAll(queryParams); } } if (dispatcher == null) { response.sendError(404, "result '" + finalLocation + "' not found"); return; } Boolean insideActionTag = (Boolean)ObjectUtils.defaultIfNull(request.getAttribute("struts.actiontag.invocation"), Boolean.FALSE); if (!insideActionTag.booleanValue() && !response.isCommitted() && request.getAttribute("javax.servlet.include.servlet_path") == null) { request.setAttribute("struts.view_uri", finalLocation); request.setAttribute("struts.request_uri", request.getRequestURI()); dispatcher.forward(request, response); } else { dispatcher.include(request, response); } } } private Map<String, Object> getParameters(ActionInvocation invocation) { return (Map)invocation.getInvocationContext().getContextMap().get("parameters"); } }
最终执行的是request.getRequestDispatcher(location).forward(req, resp)方法,相当于一个处理请求的最终servlet
因此,可以自定义结果视图类型,灵活的完成各种输出:
下面以输出验证码为例:
jar包:ValidateCode.jar
package cn.getword.result; import cn.dsna.util.images.ValidateCode; import com.opensymphony.xwork2.ActionInvocation; import org.apache.struts2.ServletActionContext; import org.apache.struts2.dispatcher.StrutsResultSupport; import javax.servlet.http.HttpServletResponse; /** * 自定义结果视图类型 */ public class CaptchaResult extends StrutsResultSupport { //配置图像的大小 private int width; private int height; @Override protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { // TODO Auto-generated method stub /*** * 1.导入第三方验证码生成包 * 2.创建ValidateCode对象 * 3.获取相应对象 * 4.输出验证码 */ ValidateCode code=new ValidateCode(width,height,4,150); HttpServletResponse response= ServletActionContext.getResponse(); // response.getWriter().print("hello world"); code.write(response.getOutputStream()); } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } }
在struts.xml中配置使用自定义结果视图类型:
<!-- 验证码 --> <package name="validateCode" namespace="/validate" extends="struts-default"> <!-- 自定义结果视图类型 局部 --> <result-types> <result-type name="catpcha" class="cn.getword.result.CaptchaResult"></result-type> </result-types> <!-- 使用默认动作类ActionSupport来处理,默认返回success,返回的结果视图类型为自定义结果视图catpcha(类似Servlet类), --> <action name="captcha"> <!-- 视图结果处理类类似于一个Servlet类,不同的类型完成不同的任务,如请求转发、重定向、自定义处理类型 --> <result name="success" type="catpcha"> <!-- 配置图像的大小,依赖注入 --> <param name="width">200</param> <param name="height">50</param> </result> </action> </package>
运行结果:
3.package标签
package:
name:包名,唯一
extends:继承父包,默认继承struts-default,在struts-default.xml中配置
abstract:是否为抽象包,抽象包中不允许出现action动作
namespace:命名空间,模块化管理,必须以 / 开头,第一个字符必须是字母,
默认值:""
4.action标签
name:
class:
method:
action 的三种访问方式:
- 全匹配方式:
- 通配符:
基本用法:
<package name="user" extends="struts-default" namespace="/user"> <!-- {1}:表示第一个匹配到的* --> <action name="*User" class="cn.getword.action.UserAction" method="{1}User"> <result name="success" type="dispatcher">/index.jsp</result> </action> </package>
更彻底的方式:
<package name="user" extends="struts-default" namespace="/user"> <!-- {1}:表示第一个匹配到的* --> <action name="*_*" class="cn.getword.action.{2}Action" method="{1}{2}"> <result name="success" type="dispatcher">/index.jsp</result> </action> </package>
- 动态方法调用
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<action name="user" class="cn.getword.action.UserAction"> <result name="success" type="dispatcher">/index.jsp</result> </action>
<a href="/user?addUser"> : user对应action的name属性,addUser对应动作类的方法
5.动作类的创建方式
(1)无侵入低耦合的方式 POJO 【基本不用】
(2)实现Action接口,实现excute方法【默认动作方法】
package cn.getword.action; import com.opensymphony.xwork2.Action; public class MainAction implements Action{ @Override public String execute() throws Exception { return null; } }
(3)继承ActionSupport类
默认的动作类为:ActionSupport
默认动作方法:excute方法
6.result结果视图
常用逻辑结果视图:
chain:转发到action【同包】
dispatcher:只能转发到物理视图
redirect:url原封不动的返回给客户端,客户端再次使用这个地址发起请求。
redirectAction:重定向到action【同包或不同包】,默认会自动加上后缀名
全局结果视图:
<package name="mydefault" extends="struts-default" abstract="true"> <global-results> <result name="error">/WEB-INF/views/error.jsp</result> </global-results> </package> <package name="p2" extends="mydefault"> <action name="index" class="cn.getword.action.MainAction" method="index"> <result name="success">/index.jsp</result> </action> <action name="login"> <result type="dispatcher">/WEB-INF/views/login.jsp</result> </action> </package>
JSONResult结果视图:
(1)jar包:struts2-json-plugin...jar
(2)struts.xml
<package name="p1" extends="json-default"> <action name="getUsers" class="cn.getword.action.UserAction" method="allUsers"> <result type="json"> <param name="noCache">true</param> <param name="contentType">application/json</param> </result> </action> </package>
(3)UserAction
package cn.getword.action; import cn.getword.domain.User; import com.opensymphony.xwork2.ActionSupport; import java.util.ArrayList; import java.util.List; public class UserAction extends ActionSupport { private List<User> users; private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; } public String allUsers(){ user = new User(){{ setId(1); setUsername("admin"); setAge(12); }}; users = new ArrayList<User>(); users.add(new User(){{ setId(1); setUsername("admin"); setAge(12); }}); users.add(new User(){{ setId(2); setUsername("张三"); setAge(21); }}); return SUCCESS; } }
(4)结果
7.访问Servlet API
方式一:使用工具类:ServletActionContext【常用】
public class MainAction extends ActionSupport { public String index(){ ServletActionContext.getRequest(); //struts2提供的包装类 ServletActionContext.getRequest(); ServletActionContext.getRequest().getSession(); ServletActionContext.getServletContext(); return ERROR; } }
方式二:通过实现接口的方式。拦截器ServletConfig封装参数
public class MainAction extends ActionSupport implements ServletRequestAware{ private HttpServletRequest request; public String index(){ System.out.println(request); return ERROR; } @Override public void setServletRequest(HttpServletRequest request) { this.request = request; } }
第三种方式:上面两种方式的底层都是通过ActionContext的get(key)获取的
ActionContext
8.请求参数的封装
ParameterInterceptor拦截器做了数据类型转换。【注意,请求参数封装到动作类实例的成员变量中,是拦截器干的事情】
方式一:属性驱动-没有实体类
注意:动作类中定义对应的成员变量,必须要有set和get 方法
对于多个name相同的值转化成数组
public class User1Action extends ActionSupport { private String username; private int age; private Date birthday; private String[] hobby; public String register(){ System.out.println(username+","+age+","+birthday+","+ Arrays.toString(hobby)); return "register"; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String[] getHobby() { return hobby; } public void setHobby(String[] hobby) { this.hobby = hobby; } }
表单:
<form method="post" action="${pageContext.request.contextPath}/user/register"> username:<input type="text" name="username"><br> age:<input type="text" name="age"><br> birthday:<input type="text" name="birthday"><br> hobby:<input type="checkbox" name="hobby" value="篮球">篮球 <input type="checkbox" name="hobby" value="足球">足球 <input type="checkbox" name="hobby" value="排球">排球<br> <input type="submit" value="提交" /> </form>
使用场景:不需要实体类的情况,如分页、查询等,不需要存储到数据库的表单数据。
方式二:属性驱动:有实体类 【不常用】
在动作类中定义一个实体类成员变量,提供get、set方法。
Action:
public class User2Action extends ActionSupport { private User user; public String register(){ System.out.println(user); return SUCCESS; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
表单的name为user.xxx【OGNL表达式】
Form:
<form method="get" action="${pageContext.request.contextPath}/user/register"> username:<input type="text" name="user.username"><br> age:<input type="text" name="user.age"><br> birthday:<input type="text" name="user.birthday"><br> hobby:<input type="checkbox" name="user.hobby" value="篮球">篮球 <input type="checkbox" name="user.hobby" value="足球">足球 <input type="checkbox" name="user.hobby" value="排球">排球<br> <input type="submit" value="提交" /> </form>
方式三:模型驱动,实现ModelDriven<>接口 【常用】
规范:实现ModelDriver接口,模型属性必须new。
public class User3Action extends ActionSupport implements ModelDriven<User> { private User user = new User(); public String register(){ System.out.println(user); return SUCCESS; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public User getModel() { return user; } }
集合类型的参数封装:
集合类型的参数只能通过属性驱动来封装。【OGNL】
(1)list集合 name: users[0].xxx
action:
public class User4Action extends ActionSupport { private List<User> users; public String register(){ System.out.println(users); return SUCCESS; } public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; } }
form:
<form method="get" action="${pageContext.request.contextPath}/user/register"> username:<input type="text" name="users[0].username"><br> age:<input type="text" name="users[0].age"><br> birthday:<input type="text" name="users[0].birthday"><br> hobby:<input type="checkbox" name="users[0].hobby" value="篮球">篮球 <input type="checkbox" name="users[0].hobby" value="足球">足球 <input type="checkbox" name="users[0].hobby" value="排球">排球<br> <input type="submit" value="提交" /> </form>
(2)Map对象
OGNL: name : users['key'].xxx
action:
public class User5Action extends ActionSupport { private Map<String, User> users; public String register(){ for (String k : users.keySet()) { System.out.println("key:"+k+", value:"+ users.get(k)); } return SUCCESS; } public Map<String, User> getUsers() { return users; } public void setUsers(Map<String, User> users) { this.users = users; } }
form:
<form method="get" action="${pageContext.request.contextPath}/user/register"> username:<input type="text" name="users['user1'].username"><br> age:<input type="text" name="users['user1'].age"><br> birthday:<input type="text" name="users['user1'].birthday"><br> hobby:<input type="checkbox" name="users['user1'].hobby" value="篮球">篮球 <input type="checkbox" name="users['user1'].hobby" value="足球">足球 <input type="checkbox" name="users['user1'].hobby" value="排球">排球<br> <input type="submit" value="提交" /> </form>
9.请求封装异常处理【封装失败,如日期转换失败,数字转换等】,数据回显(界面太丑,基本不用)
适用于模型驱动,有实体类的属性驱动
当ParameterInterceptor做类型转化时发生错误,将返回input逻辑视图,因此只要在action中配置input结果视图即可
struts.xml:
<package name="p3" extends="struts-default" namespace="/user"> <action name="register" class="cn.getword.action.User3Action" method="register"> <result name="success">/WEB-INF/views/success.jsp</result> <result name="input" type="redirect">/user/register</result> </action> </package>
action:
public class User3Action extends ActionSupport implements ModelDriven<User> { private User user = new User(); public String register(){ System.out.println(user); return SUCCESS; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public User getModel() { return user; } }
form:
或者使用struts提供的form表单:
<%--自动补齐项目前缀--%> <s:form action="user/register"> <s:textfield name="username" label="姓名" /> <s:textfield name="age" label="年龄" /> <s:textfield name="birthday" label="生日" /> <s:checkbox name="hobby" value="篮球" fieldValue="篮球" label="篮球" /> <s:checkbox name="hobby" value="足球" fieldValue="足球" label="足球" /> <s:checkbox name="hobby" value="排球" fieldValue="排球" label="排球" /> <s:submit value="提交" /> </s:form>
将提示信息改为中文:在模型类的同目录下创建User.properties属性文件。
invalid.fieldvalue.birthday=日期格式不对
在实际项目开发中,需要在前端进行表单验证,因此传到后台的数据基本合法,如何不合法(转换失败),需要提供input结果视图,以防止出现尴尬的界面(struts的报错界面)
表单数据被保存在request域中,因此可以使用EL表达式进行数据回显。
<form method="post" action="${pageContext.request.contextPath}/user/register"> username:<input type="text" name="username" value="${username}"><br> age:<input type="text" name="age" value="${age}"><br> birthday:<input type="text" name="birthday" value="${birthday}" ><br> hobby:<input type="checkbox" name="hobby" value="篮球">篮球 <input type="checkbox" name="hobby" value="足球">足球 <input type="checkbox" name="hobby" value="排球">排球<br> <input type="submit" value="提交" /> </form>
使用此方法,可以解决struts2的标签带来的bug,例如:封装失败后,第一次返回input结果视图,第二次返回error结果视图
此外,还发现一个问题,struts2对于post和get请求没有过滤区分?
end