流程介绍
我们模拟一个请求/响应的情景,来介绍Struts的工作流程。注意,下面的序号和图中的序号没有严格的对应关系。
- 浏览器向系统发出请求,请求的地址是ac.action
- 请求被StrutsPreparedExecuteFilter拦截,去掉.action后缀,所得结果ac作为action的name。
在Struts框架中,负责处理用户请求的称为action,这里的name用于获取action,找到他,让他干活。 - StrutsPreparedExecuteFilter在struts.xml中查找action映射相关的配置,根据ac查到action的类pkg.AcAction。
- StrutsPreparedExecuteFilter实例化pkg.AcAction,并调用其execute方法。
- pkg.AcAction工作完成之后,汇报工作结果:返回一个字符串success,称之为result。
- StrutsPreparedExecuteFilter使用success去查struts.xml中的结果映射部分,获取到对应的物理视图资源是ac-success.jsp。
- StrutsPreparedExecuteFilter使用forward的方式,将ac-success.jsp展示给用户。
仔细看一下上面的图和内容,在脑海中回忆一下整个流程,最好能够闭眼将整个流程复述一遍,然后再继续。
我们的工作
在上述的流程描述中,几乎都是框架要做的事,但是为了能让框架顺利的工作,我们要提供支持性的工作。使用框架基本都是这样,我们按照框架的模式提供支持,框架自行工作。
接下来看一下我们需要做的事:
- 配置StrutsPreparedExecuteFilter。
StrutsPreparedExecuteFilter是整个流程中的核心,这个指挥中心不是自行启动的,我们需要在web.xml中启动它。这件事只需要做一次。 - 创建Action类
action负责处理用户的请求,具体怎么处理,需要我们创建一个Action类来实现。 - 创建视图
Action实现之后,我们要考虑返回怎样的视图给用户。在这里,我们需要创建1到多个jsp文件,担任视图的角色。 - 配置action映射
配置name和class的对应关系,让Struts知道该把哪些请求分派给哪个action。 - 配置result映射
Action返回的是一个普通的字符串,我们称之为处理结果或者逻辑视图,不管叫什么,总之它不是物理视图,不指定任何视图文件。Struts为了将Action类和视图文件解耦,将返回结果和物理视图的对应关系,我们称之为result映射,在配置文件中配置。
上面五个工作,第一个只需要做一次,相对的,后面四个每创建一个action都需要做一次。
在上面五个工作中,视图文件是jsp,我们将之视为基本知识,并不打算介绍。result映射一般是在action映射内部配置的,所以配置result将包含在配置actioin中。所以,我计划分下面三个主题,介绍上面的工作:
- 配置核心过滤器
- 创建Action
- 配置action
1.配置核心过滤器
这里使用Maven管理项目,如果要使用Struts框架,你需要引入依赖。你可以在http://mvnrepository.com/中输入struts2-core来查找可用的版本,从中选择一个并获取其依赖配置:
<dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-core</artifactId> <version>2.3.28</version> </dependency>
在web.xml中配置下面的内容:
1 <filter> 2 <filter-name>struts2</filter-name> 3 <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>struts2</filter-name> 7 <url-pattern>/*</url-pattern> 8 </filter-mapping>
2.创建Action
2.1.参数获取
2.2.创建Action
- POJO
Action不需要实现任何接口,不需要继承任何类,但是需要包含一个方法:public String execute() throws Exception。这个方法是处理请求的入口,由Struts框架调用。这个方法的名字可以自行定义,但是execute是默认的名字,使用这个可以省去一项属性配置。 - 实现Action接口
全称com.opensymphony.xwork2.Action,这个接口定义了几个字符串常量,作为result;还定义了execute方法。 - 继承ActionSupport
全称com.opensymphony.xwork2.ActionSupport,实现了Action。
2.3.demo设计
为了介绍上面3种方式,这里设计一个demo。
2.3.1.POJO
1 package cn.ljl.note.struts2.login.actions; 2 3 public class LoginPOJO { 4 private static final String VALID_USER = "admin"; 5 private static final String VALID_PWD = "admin"; 6 7 private static final String SUCCESS = "success"; 8 private static final String LOGIN = "login"; 9 10 private String username; 11 private String password; 12 private String tip; 13 14 public String getUsername() { 15 return username; 16 } 17 public void setUsername(String username) { 18 this.username = username; 19 } 20 public String getPassword() { 21 return password; 22 } 23 public void setPassword(String password) { 24 this.password = password; 25 } 26 public String getTip() { 27 return tip; 28 } 29 public void setTip(String tip) { 30 this.tip = tip; 31 } 32 33 public String execute() throws Exception { 34 boolean validUser = VALID_USER.equals(getUsername()); 35 boolean validPwd = VALID_PWD.equals(getPassword()); 36 37 if (!validUser) { 38 setTip("用户不存在!"); 39 return LOGIN; 40 } 41 42 if (!validPwd) { 43 setTip("密码不正确!"); 44 return LOGIN; 45 } 46 47 setTip(null); 48 return SUCCESS; 49 } 50 }
2.3.2.实现Action接口
Action接口的类图如下:
源代码:
1 package cn.ljl.note.struts2.login.actions; 2 3 import com.opensymphony.xwork2.Action; 4 5 public class LoginAction implements Action{ 6 private static final String VALID_USER = "admin"; 7 private static final String VALID_PWD = "admin"; 8 9 private String username; 10 private String password; 11 private String tip; 12 13 public String getUsername() { 14 return username; 15 } 16 public void setUsername(String username) { 17 this.username = username; 18 } 19 public String getPassword() { 20 return password; 21 } 22 public void setPassword(String password) { 23 this.password = password; 24 } 25 public String getTip() { 26 return tip; 27 } 28 public void setTip(String tip) { 29 this.tip = tip; 30 } 31 32 @Override 33 public String execute() throws Exception { 34 boolean validUser = VALID_USER.equals(getUsername()); 35 boolean validPwd = VALID_PWD.equals(getPassword()); 36 37 if (!validUser) { 38 setTip("用户不存在!"); 39 return LOGIN; 40 } 41 42 if (!validPwd) { 43 setTip("密码不正确!"); 44 return LOGIN; 45 } 46 47 setTip(null); 48 return SUCCESS; 49 } 50 51 }
2.3.3.继承ActionSupport类
1 package cn.ljl.note.struts2.login.actions; 2 3 import com.opensymphony.xwork2.ActionSupport; 4 5 public class LoginActionSupport extends ActionSupport { 6 7 private static final long serialVersionUID = 8451980703294866793L; 8 9 private static final String VALID_USER = "admin"; 10 private static final String VALID_PWD = "admin"; 11 12 private String username; 13 private String password; 14 private String tip; 15 16 public String getUsername() { 17 return username; 18 } 19 public void setUsername(String username) { 20 this.username = username; 21 } 22 public String getPassword() { 23 return password; 24 } 25 public void setPassword(String password) { 26 this.password = password; 27 } 28 public String getTip() { 29 return tip; 30 } 31 public void setTip(String tip) { 32 this.tip = tip; 33 } 34 35 @Override 36 public String execute() throws Exception { 37 boolean validUser = VALID_USER.equals(getUsername()); 38 boolean validPwd = VALID_PWD.equals(getPassword()); 39 40 if (!validUser) { 41 setTip("用户不存在!"); 42 return LOGIN; 43 } 44 45 if (!validPwd) { 46 setTip("密码不正确!"); 47 return LOGIN; 48 } 49 50 setTip(null); 51 return SUCCESS; 52 } 53 54 }
2.4.三种方式的比较
3.配置action
3.1.配置文件
1 <?xml version="1.0" encoding="GBK"?> 2 <!DOCTYPE struts PUBLIC 3 "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" 4 "http://struts.apache.org/dtds/struts-2.3.dtd"> 5 <struts> 6 7 </struts>
3.2.命名空间
一次请求的地址,像这个样子:
<package name="login" extends="struts-default" namespace="/login"> ...... </package>
- name
name唯一标识一个package - extends
package有继承的特性,使用extends指定另一个package的name,就会继承彼package下所定义的内容。
这里继承的struts-default是在struts2-core的struts-default.xml中定义的。通常,建议继承struts-default。 - namespace
配置命名空间。
默认的命名空间
namespace不是必需的属性,如果没有配置,那就是默认的命名空间。默认的命名空间有特殊的作用:如果在请求的URL中解析出来的命名空间里找不到对应的action,就到默认的命名空间里找。默认命名空间是一个抽象的概念,不是默认值,你不能说“我可以通过配置namespace等于默认值,来指定默认命名空间”。比如"/"被称为根命名空间,但是它不是默认命名空间,它也没有任何特殊的性质,就和其他命名空间一样。
3.3.action
即配置action映射,struts框架需要查找这个映射,才能根据URL找到实际的处理Action。action是在<package>元素内配置的,下面是一个demo:
<action name="check" class="cn.ljl.note.struts2.login.actions.LoginActionSupport" method="execute"> ...... </action>
- name
它同时也是action的url请求地址的一部分,同一个命名空间下,action的name要唯一 - class
它是Action类,负责处理用户的请求。这是一个非必需的属性,默认为com.opensymphony.xwork2.ActionSupport;你可以看下这个类的execute方法,只是直接返回SUCCESS。 - method
它是Action的方法名,对于框架来说,相当于回调方法。框架会调用这个方法,以达到通知请求到达的效果。这是一个非必需的属性,默认值为execute,所以上面的demo完全不用配置这个属性。
3.4.result
即配置result映射,根据这个映射,struts框架才能根据Action返回的逻辑结果(字符串)找到对应的视图资源。result是在<action>元素内配置的,下面是一个demo:
<result name="success">/index.jsp</result> <result name="login">/login/login.jsp</result>
<result>元素的属性name代表Action返回的逻辑结果;<result>体的内容,代表物理视图的路径。其中name的默认值是"success",所以第1行不用配置name属性。
3.5.异常
1 public String execute() { 2 try { 3 // ... 4 } catch(异常1 e1) { 5 return 结果1; 6 } catch(异常2 e2) { 7 return 结果2; 8 } 9 }
然后我们会在struts.xml中这样配置result映射:
<result name="结果1">视图1.jsp</result> <result name="结果2">视图2.jsp</result>
这样是可以的,实际上在我们已知的知识上,想到这种方式来解决新的问题,能体现我们是会灵活变通的。不过Struts也提供了正统的配置方法,让我们只需要配置异常和返回结果的映射关系,而不需要捕获Action处理方法中抛出的异常。
3.5.1.异常映射
在<action>元素下,使用<exception-mapping>元素来配置,下面上一个demo:
<result name="exception">/exception/exception.jsp</result> <exception-mapping result="exception" exception="java.lang.Exception" />
- result
对应的异常类型发生时,要返回什么逻辑结果 - exception
配置什么类型的异常
- 配置的异常类型,对其子类型异常是否有效?
- 两种异常类型(继承关系)配置的先后顺序,对异常抛出的返回结果是否有影响?
抛出的异常可以是父类型、子类型、两者的子类型。
3.5.2.拦截器
我们虽然提供了异常到逻辑结果的映射,但是还需要一个拦截器来做这样的工作:拦截抛出的异常,查映射关系,改为返回对应的逻辑结果。这样的拦截器已经在struts-default中使用了,所以只需要保证定义的<package>,直接或间接的继承了struts-default就好了
3.5.3.输出异常
我们可以在jsp中使用el来输出异常,像这样${exception }。不过,Struts2也提供了相关的标签,下面是一个demo:
1 <%@ page language="java" contentType="text/html; charset=GBK" 2 pageEncoding="GBK"%> 3 <%@ taglib uri="/struts-tags" prefix="s" %> 4 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 5 <html> 6 <body> 7 <s:property value="exceptionStack"/> 8 </body> 9 </html>
- exception
输出异常本身,相当于${exception } - exception.message
输出异常的信息,相当于${exception.message } - exceptionStack
输出异常的堆栈信息,正是demo中所用到的。
3.6.总结
好了,让之前出现过的几位也上来吧,我们来张合照:struts.xml
1 <?xml version="1.0" encoding="GBK"?> 2 <!DOCTYPE struts PUBLIC 3 "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" 4 "http://struts.apache.org/dtds/struts-2.3.dtd"> 5 <struts> 6 <package name="login" extends="struts-default" namespace="/login"> 7 <action name="check" class="cn.ljl.note.struts2.login.actions.LoginActionSupport"> 8 <result name="success">/index.jsp</result> 9 <result name="login">/login/login.jsp</result> 10 <result name="exception">/exception/exception.jsp</result> 11 <exception-mapping result="exception" exception="java.lang.Exception" /> 12 </action> 13 </package> 14 </struts>
4.使用config-browser查看配置
你可能想浏览配置的情况,当然对于一个简单的项目,直接看struts.xml可能更快、更专业。接下来要出场的是一个插件,config-browser,它可以提供通过前台查看配置情况的功能。这不是它唯一的优点,但是我们不需要为其优点列一个清单,暂时知道这一个吧,剩下的自己体会。
4.1.添加依赖
我们使用的struts2-core是2.3.28版本,我们也使用相同版本的config-browser:
<dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-config-browser-plugin</artifactId> <version>2.3.28</version> </dependency>
4.2.使用
重新打包、部署,重启服务,输入访问地址:http://localhost:8080/note-struts2/config-browser/actionNames.action,可以看到下面的内容:
注意左侧导航栏,Namespaces下是所有的命名空间,其中/config-browser是插件定义的,/login是我们之前定义的。右侧默认显示默认命名空间下的action。
点击左侧Namespaces/login链接,可以看到:
右侧列出了这个命名空间下的action,点进去会看到:
注意:这里的内容是按照标签页组织的,默认显示的是Results标签页,切换到Eception Mappings,可以看到:
我们关于config-browser入门的介绍就到这里了,其他的靠大家自己了。
5.扩展
前面已经对框架的基本流程中涉及到的工作,做了基本的介绍,下面这些内容会深入一下。
5.1.action
5.1.1.多个处理方法
一个Action类中可以有多个处理方法,只需要在配置<action>的时候使用method指定不同的方法名,就可以定义多个action。比如,我们假想一个Action,它的类图是这样的:
我们可以在struts.xml中这样配置:
1 <package name="imagination" extends="struts-default" namespace="/imagination"> 2 <action name="addUser" class="cn.ljl.note.struts.imaginary.actions.UserAction" method="add"> 3 ... 4 </action> 5 <action name="saveUser" class="cn.ljl.note.struts.imaginary.actions.UserAction" method="save"> 6 ... 7 </action> 8 <action name="deleteUser" class="cn.ljl.note.struts.imaginary.actions.UserAction" method="delete"> 9 ... 10 </action> 11 </package>
5.1.2.使用通配符
在满足一定的模式的情况下,使用通配符可以使用最少的配置量,配置多个action。比如对于上面的情况,也可以使用下面的配置:
1 <package name="imagination" extends="struts-default" namespace="/imagination"> 2 <action name="*User" class="cn.ljl.note.struts.imaginary.actions.UserAction" method="{1}"> 3 ... 4 </action> 5 </package>
line 2,name属性值中使用了*,method属性值中的{1}表示使用第1个*所匹配的字符串。这个配置和上文的配置效果是一样的,但是配置工作更少。
class也可以和name满足一定的模式,比如下面的配置:
1 <package name="imagination" extends="struts-default" namespace="/imagination"> 2 <action name="*User" class="cn.ljl.note.struts.imaginary.actions.{1}UserAction"> 3 ... 4 </action> 5 </package>
- 完全匹配优先于模式匹配
- 模式匹配中,前面的优先
5.1.3.默认的action
*可以匹配一切,所以我们可以使用*来配置默认的action。另外,我们还可以像下面这样配置:
1 <package name="imagination" extends="struts-default" namespace="/imagination"> 2 <default-action-ref name="default" /> 3 <action name="default" class="..."> 4 ... 5 </action> 6 ... 7 </package>
在特定命名空间下配置,只能作为当前命名空间的默认action;在默认的命名空间下配置,可以作为全局的默认action。
5.1.4.默认的class
配置<action>时,默认的class是com.opensymphony.xwork2.ActionSupport,你可以修改这项配置:在<package>下添加<default-class-ref class="" />。
5.1.5.动态方法调用
在form标签的action属性,可以同时指定action的name和方法,比如:
<form action="user!add"> ... </form>
就指定name为user的action的add方法,来处理请求。
5.2.result
Struts2支持多种result-type,基本的result-type都是在struts2-core的struts-default.xml中配置的。
5.2.1.多种结果类型
1.dispatcher
disparcher是默认的result-type,以指定的jsp作为视图。最原始的配置方式是这样的:
<result name="success" type="dispatcher"> <param name="location">/success.jsp</param> </result>
因为result的name默认就是success,type默认就是dispatcher,而视图的location可以直接在<result>的体配置。所以可以简化成这个样子:
<result>/success.jsp</result>
2.plainText
plainText将指定的视图以文本的形式显示给浏览器。使用这种方式,需要指定视图的location;如果视图文件中包含非西欧字符,还要指定charSet。
<result name="success" type="plainText"> <param name="location">/success.jsp</param> <param name="charSet">GBK</param> </result>
3.redirect
重定向。这个类型可以指定一个location,浏览器重定向到指定的视图。
<result name="success" type="redirect">/index.jsp</result>
4.redirectAction
重定向到Action。这个类型专门重定向到Action,与redirect算是被包含关系。这个类型可以指定namespace和actionName:
<result type="redirectAction"> <param name="namespace">/login</param> <param name="actionName">check</param> </result>
暂时,就介绍这几种吧。
5.2.2.使用通配符
在配置result映射的时候,也可以使用通配符,比如:
<package name="imagination" extends="struts-default" namespace="/imagination"> <action name="*User" class="cn.ljl.note.struts.imaginary.actions.UserAction" method="{1}"> <result>/imagination/result-{1}.jsp</result> </action> </package>
按照上面的配置,addUser对应cn.ljl.note.struts.imaginary.actions.UserAction的add方法,返回的视图是/imagination/result-add.jsp。
5.2.3.使用OGNL表达式
OGNL表达式的具体内容,计划放在后面讲,这里只介绍几个简单的用法。配置result映射时,可以使用action的属性值(要提供getter方法),比如:
<result>/imagination/result-add.jsp?username=${username}</result>
在计算物理视图时,就会使用action的username属性,替换其中的${username}。
如果属性是复杂属性,比如bean,而在result中需要的是属性bean的属性,也可以按照这样的方式获取:${user.name}。
5.2.4.全局配置
在之前的配置里,都是针对action的一个逻辑结果进行配置;全局配置是在<package>范围,提供一个配置,对所有action都有效。
全局配置是在<package>下,使用<global-results> - <result>来配置,比如:
1 <package ...> 2 <global-results> 3 <result name="exception">/exception.jsp</result> 4 </global-results> 5 ... 6 </package>
这样一来,这个package下所有的action,如果没有指定"exception"的映射,就使用全局的映射;如果指定了,就覆盖全局的配置。
5.3.异常的全局配置
在<package>范围,异常也可以使用全局配置。使用<global-exception-mappings> - <exception-mapping>。异常映射是把异常的类型映射到result,所以它依赖于result,全局的异常映射应该只使用全局的result,像下面这样:
1 <package ...> 2 <global-results> 3 <result name="exception">/exception.jsp</result> 4 </global-results> 5 <global-exception-mappings> 6 <exception-mapping exception="java.lang.Exception" result="exception" /> 7 </global-exception-mappings> 8 ... 9 </package>