简单介绍Struts2 MVC的工作流程。

流程介绍


 

我们模拟一个请求/响应的情景,来介绍Struts的工作流程。注意,下面的序号和图中的序号没有严格的对应关系。

  1. 浏览器向系统发出请求,请求的地址是ac.action
  2. 请求被StrutsPreparedExecuteFilter拦截,去掉.action后缀,所得结果ac作为action的name。
    在Struts框架中,负责处理用户请求的称为action,这里的name用于获取action,找到他,让他干活。
  3. StrutsPreparedExecuteFilter在struts.xml中查找action映射相关的配置,根据ac查到action的类pkg.AcAction。
  4. StrutsPreparedExecuteFilter实例化pkg.AcAction,并调用其execute方法。
  5. pkg.AcAction工作完成之后,汇报工作结果:返回一个字符串success,称之为result。
  6. StrutsPreparedExecuteFilter使用success去查struts.xml中的结果映射部分,获取到对应的物理视图资源是ac-success.jsp。
  7. StrutsPreparedExecuteFilter使用forward的方式,将ac-success.jsp展示给用户。

仔细看一下上面的图和内容,在脑海中回忆一下整个流程,最好能够闭眼将整个流程复述一遍,然后再继续。

我们的工作

在上述的流程描述中,几乎都是框架要做的事,但是为了能让框架顺利的工作,我们要提供支持性的工作。使用框架基本都是这样,我们按照框架的模式提供支持,框架自行工作。
接下来看一下我们需要做的事:

  1. 配置StrutsPreparedExecuteFilter。
    StrutsPreparedExecuteFilter是整个流程中的核心,这个指挥中心不是自行启动的,我们需要在web.xml中启动它。这件事只需要做一次。
  2. 创建Action类
    action负责处理用户的请求,具体怎么处理,需要我们创建一个Action类来实现。
  3. 创建视图
    Action实现之后,我们要考虑返回怎样的视图给用户。在这里,我们需要创建1到多个jsp文件,担任视图的角色。
  4. 配置action映射
    配置name和class的对应关系,让Struts知道该把哪些请求分派给哪个action。
  5. 配置result映射
    Action返回的是一个普通的字符串,我们称之为处理结果或者逻辑视图,不管叫什么,总之它不是物理视图,不指定任何视图文件。Struts为了将Action类和视图文件解耦,将返回结果和物理视图的对应关系,我们称之为result映射,在配置文件中配置。

上面五个工作,第一个只需要做一次,相对的,后面四个每创建一个action都需要做一次。

在上面五个工作中,视图文件是jsp,我们将之视为基本知识,并不打算介绍。result映射一般是在action映射内部配置的,所以配置result将包含在配置actioin中。所以,我计划分下面三个主题,介绍上面的工作:

  1. 配置核心过滤器
  2. 创建Action
  3. 配置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.参数获取

既然要在Action中处理用户请求,那么Action就需要获取用户的请求参数。我们需要为此在Action中添加对应的属性,属性的name和请求参数的name一致,类型是基本类型;另外,我们要为这个属性添加setter方法。
如果为Action的属性提供getter方法,在jsp可以从request的属性中获取该属性。

2.2.创建Action

有三种方式可以创建Action。
  1. POJO
    Action不需要实现任何接口,不需要继承任何类,但是需要包含一个方法:public String execute() throws Exception。这个方法是处理请求的入口,由Struts框架调用。这个方法的名字可以自行定义,但是execute是默认的名字,使用这个可以省去一项属性配置。
  2. 实现Action接口
    全称com.opensymphony.xwork2.Action,这个接口定义了几个字符串常量,作为result;还定义了execute方法。
  3. 继承ActionSupport
    全称com.opensymphony.xwork2.ActionSupport,实现了Action。

2.3.demo设计

为了介绍上面3种方式,这里设计一个demo。

以登录为例,用户填写账号和密码,提交登录请求。Action对账号密码进行权限验证,通过则跳转到首页,否则依旧跳转到登录页面。
所以对于Action来说,就是获取请求参数:账号、密码。判断账号是否存在,密码是否正确。如果通过返回一个”success”的逻辑结果,否则返回”login”的逻辑结果。
为了添加一点人性化的元素,我们将区分“账号不存在”和“密码错误”两种情况,并分别进行提示。
好了,我们开始吧。

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 }

LoginAction

 

2.3.3.继承ActionSupport类

com.opensymphony.xwork2.ActionSupport是一个复杂的类,它提供了很多其他的功能,而这些我们目前还不需要关注。所以这里只要把它当成Action接口的默认实现类就好了,这里也不再贴出其类图。
demo的源代码:

 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 }

LoginActionSupport

2.4.三种方式的比较

比较上述三种方式的源代码,大部分代码都是重复的。实现Action接口或者继承ActionSupport类,可以直接使用已经定义好的逻辑结果,而这些一般是比较常用的。
通过继承ActionSupport来开发Action,这是建议的方式。

3.配置action


3.1.配置文件

Struts2的常规配置文件是struts.xml,这个文件放在源文件夹的根目录。比如使用maven,应该把它放在src/main/resources下。
配置文件的结构像下面这样:
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>
其中,文档声明中的版本号与当前使用的struts2的版本有关。也可以从struts2-core相关jar包中拷贝。
下面将要介绍的内容,基本是在这里配置的,否则会特别说明。另外,如果你修改了这里的配置,编译部署之后,需要重启服务器。

3.2.命名空间

一次请求的地址,像这个样子:

http://localhost:8080/note-struts2/login/check.action
在Struts2看来,/login称为命名空间;check称为action-name;.action是后缀,在解析的时候会自动去掉。
命名空间可以多级,就像目录结构一样,一层层;action-name就像是文件名。
在struts.xml中,使用<package>的namespace属性来指定命名空间,所以action的定义都是在<package>下定义的。比如,我们可以这样定义:
<package name="login" extends="struts-default" namespace="/login">
  ......
</package>

 

上述代码片段,涉及到3个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>
<action>元素有3个基本的属性:
  • 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.异常

在Action中出现异常,可能希望根据不同的异常类型跳转到不同的物理视图。
结合Struts2框架的工作流程,我们可以Action中捕获异常,根据不同的类型返回不同的字符串,并在struts.xml中根据这些返回的结果配置不同的物理视图。比如我们可能会这样写Action的方法:
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" />
这个demo的意思是,如果处理方法抛出了java.lang.Exception异常,就返回”exception”的逻辑结果,它对应的物理视图是/exception/exception.jsp。
<exception>有两个属性:
  • result
    对应的异常类型发生时,要返回什么逻辑结果
  • exception
    配置什么类型的异常
留两个有趣的问题:
  1. 配置的异常类型,对其子类型异常是否有效?
  2. 两种异常类型(继承关系)配置的先后顺序,对异常抛出的返回结果是否有影响?
    抛出的异常可以是父类型、子类型、两者的子类型。
这两个是比较细节、相对啰嗦的问题,我不喜欢这样的问题,实际遇到的时候,我会顺手测一下,但是目前,我还不关心它们的答案。

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>
line 3,引入了struts的标签库,定义了前缀为s。
line 7,使用<property>标签,输出了异常的堆栈信息。
<property>标签在用于输出异常信息是,value属性有下面几个选择:
  • 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>
此时,AddUser将映射到cn.ljl.note.struts.imaginary.actions.AddUserAction。
其实,上述两种配置,体现了两种思想:将不同的业务交给Action的不同的处理方法,或者干脆使用不同的Action。
 
优先级
使用通配符会存在这样的问题:请求的地址为addUser.action,struts.xml中同时有addUser、*User、*可以匹配这个请求,会选择哪一个来处理请求呢?
如果我们称不使用通配符的为完全匹配,称使用通配符的为模式匹配,那么有下面两个规则:
  • 完全匹配优先于模式匹配
  • 模式匹配中,前面的优先
所以,会选择addUser。如果没有这个配置,则在*User、*中,哪一个出现在struts.xml的前面,哪一个被选择。
所以,建议把范围更广的配置放在后面。

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方法,来处理请求。

这种方式,前台依赖于服务端的API,这样是不好的。

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>

 

 

版权声明:本文为ywjy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ywjy/p/5635635.html