SSH框架-初识Struts2

Author Avatar
Orange 4月 05, 2018
  • 在其它设备中阅读本文章

概述

最初的Java Web开发由 JavaBean + JSP 模式实现,大量逻辑视图代码混杂于JSP中,造成代码可维护性极差。
而后Sun公司推出典型MVC模式,将逻辑与视图代码分离出来。

MVC模式

MVC是一种软件设计典范,将软件设计分为三大部分:(Model)模型 + (View)视图 + (Controller)控制器。以此组织代码,方便维护和修改。
Sun公司推出的MVC实现方式为 JavaBean + JSP + Servlet,迅速推进Java Web开发变革。而后各大MVC模式实现方式层出不穷,为达成统一,MVC框架由此而生。Struts便是其中流行的一种。

Struts简史

Struts项目隶属Apache基金会,出身名门。Struts前身为Struts1,于2001年推出,随即风靡全球。Struts1运行流程图:
Struts1运行流程

WebWork也是同时代产物,由于技术发展飞快,Struts项目开发组织舍弃陈旧的Struts1模式转而于2007年设计出基于WebWork的全新Struts2框架。Struts1基于Servlet API的控制器设计导致与JSP/Servlet耦合非常紧密,造成很多缺陷,而WebWork采用过滤器(filter)实现,取消对Servlet API的依赖使测试更加方便。Struts2便是WebWork的升级版。在了解Struts2之前本文将先阐述过滤器(filter)。

过滤器(filter)

Filter技术是Servlet技术中最激动人心的技术之一,开发人员通过Filter技术,对web服务器的web资源进行拦截,从而实现一些如权限控制、过滤词汇、压缩信息等特殊功能。在一个web应用中,可编写多个Filter,这些Filter组合起来称之为一个Filter链。当调用FilterChain对象的doFilter方法时,web服务器会检查FilterChain对象中是否还有filter,若有则调用第下个filter,否则调用实现servlet接口对象的service方法。Filter工作原理如下所示:
Filter工作原理

Struts2原理

Struts2基于过滤器(filter)实现,其工作原理如下:
Struts2框架原理
图中名词解释:

  • ActionContextCleanUp:Action属性清除过滤器,用于清除Action属性,而不用Action自行清除,以此延长Action属性(包括自定义属性)生命周期,以便在JSP页面中进行访问
  • Other Filters(SiteMesh, etc):其他可附加的过滤器,诸如用于响应页面修饰的SiteMesh过滤器等
  • StrutsPrepareAndExecuteFilter:此过滤器为Struts2核心,用于调用Action映射(ActionMapper)类、设置编码格式、调用Action代理(ActionProxy)类加载配置信息;同时可拆分为StrutsPrepareFilter和StrutsExecuteFilter执行Action前(位置①)或后(位置②)的用户自定义过滤器
  • ActionMapper:Action映射类,用于匹配URL对应的Action或HTML或JSP
  • ActionProxy:Action代理类,用于调用ConfigurationManager读取struts配置文件
  • ConfigurationManager:配置管理类,用于读取struts相关配置文件(struts.xml、struts.properties)
  • ActionInvocation:Action调用类,用于解析OGNL表达式和依照struts配置(struts.xml)顺序调用拦截器(Interceptor)和Action实例(Struts2使用类似Filter链的拦截器栈(Interceptor Stack)顺序调用拦截器)
  • Interceptor (1)(2)(3):拦截器接口,用于对Action进行过滤请求,功能类似Filter。不同于回调函数实现的Filter,拦截器基于Java反射机制实现。拦截器使用栈结构存储,注意图中调用顺序
  • Action:Action实例,用于进行模型(Model)与页面(View)链接的逻辑操作,也可包含模型对象,故不能简单视其为控制器(Controller)
  • Result:Action执行方法的返回结果,实际为字符串,如:success、input、error等
  • Template:显示模板,用于响应页面显示,如JSP、FreeMarker、HTML等

Struts2使用

环境配置

  1. 创建Java Web项目并导入与Struts2相关的Jar开发包。开发包下载地址
  2. 配置web.xml,加入Struts2核心过滤器(filter),配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <web-app ...>
    ...
    <filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    </web-app>
  3. 在src文件夹内创建struts.xml文件,该文件用于配置全局属性、Interceptor以及action

    1
    2
    3
    4
    5
    6
    7
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
    "http://struts.apache.org/dtds/struts-2.5.dtd">
    <struts>

    </struts>

struts.xml

constant

constant标签用于配置全局属性,如:

1
2
<!--该属性指定需要Struts2处理的请求后缀,该属性的默认值是action,即所有匹配*.action或无后缀的请求都由Struts2处理。多个请求后缀之间以英文逗号(,)隔开-->
<constant name="struts.action.extension" value="action,"/>

可也置于struts.properties文件中,以键值对形式保存

1
struts.action.extension=action,

更多属性请查阅Struts2官网

package

package标签表示独立模块,每个package都有独立的interceptor、action定义
其属性abstract用于创建抽象package,namespace用于路径匹配(相当于文件夹路径),extends用于继承配置,例如

1
2
3
4
5
<!--namespace对应http://host:port/project/path1/*.action中的path1,默认为"/"-->
<!--exends表示该包继承于框架提供的名为struts-default默认配置包-->
<package name="example-package" namespace="/path1" extends="struts-default">
<!--actions...-->
</package>

关于URL中 namespace 路径的访问并非严格匹配,而是层级向上查找。例如:
当仅有单个package且其 namespace=path1 时,对于路径:

均可访问到example.action对应页面。

action

action标签用于定义URL名字对应的action类,例如

1
2
<!--此标签表示http://host:port/project/index.action网址对应HelloWorldAction类-->
<action name="index" class="com.example.project.HelloWorldAction"></action>

name属性支持正则匹配,因此可对其作些文章:

1
2
3
4
<!--此设置表示形如http://host:port/project/Example.action网址对应ExampleAction类-->
<action name="*" class="com.example.project.{1}Action">
<!--子标签也支持读取{1}-->
</action>

设定method属性值,可指定调用对应名称的方法,如:

1
2
<!--将调用HelloWorldAction类中自定义的hello()方法-->
<action name="index" method="hello" class="com.example.project.HelloWorldAction"></action>

result

result标签用于指定action方法返回值对应跳转页面(跳转默认采用forward方式,即type=”dispatcher”),例如

1
2
3
4
5
<action name="index" class="com.example.project.HelloWorldAction">
<!--当HelloWorldAction类某方法返回success则响应hello.jsp页面-->
<!--属性name默认值为success-->
<result name="success">/hello.jsp</result>
</action>

因为struts.xml支持OGNL表达式,故可取request、session、application等属性值:

1
2
3
4
<action name="index" class="com.example.project.HelloWorldAction">
<!--读取request范围中名为attrExample的属性值作为JSP文件名-->
<result name="success">/{#request.attrExample}.jsp</result>
</action>

以下为Struts2已默认包含的name属性值:

  • SUCCESS:Action正确的执行完成,返回相应的视图,success是name属性的默认值。
  • NONE:表示Action正确的执行完成,但并不返回任何视图。
  • ERROR:表示Action执行失败,返回到错误处理视图。
  • INPUT:Action的执行,需要从前端界面获取参数,INPUT就是代表这个参数输入的界面,一般在应用中,会对这些参数进行验证,如果验证没有通过,将自动返回到该视图。
  • LOGIN:Action因为用户没有登陆的原因没有正确执行,将返回该登陆视图,要求用户进行登陆验证。

以下为type属性值可选内容:

  • dispatcher:默认结果类型,用来呈现JSP页面
  • chain:将action和另外一个action链接起来
  • freemarker:呈现Freemarker模板
  • httpheader:返回一个已配置好的HTTP头信息响应
  • redirect:将用户重定向到一个已配置好的URL
  • redirectAction:将用户重定向到一个已定义好的action
  • stream:将原始数据作为流传递回浏览器端,该结果类型对下载的内容和图片非常有用
  • velocity:呈现Velocity模板
  • xslt:呈现XML到浏览器,该XML可以通过XSL模板进行转换
  • plaintext:返回普通文本类容

default-action-ref

default-action-ref标签用于指定默认action处理,常用于404页面友好设计,例如:

1
2
3
4
<default-action-ref name="default"/>
<action name="default" class="com.example.project.DefaultAction">
<result name="success">/404.jsp</result>
</action>

interceptor

interceptor标签用于拦截action请求响应,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<interceptors>
<!--此标签表示创建名为myInterceptor的拦截器对应MyInterceptor类-->
<interceptor name="myInterceptor" class="com.example.project.MyInterceptor"></interceptor>
<!--interceptor-stack标签表示自定义拦截器栈,由于使用自定义interceptor将覆盖默认拦截器栈(defaultStack),故需重新显式引入(注意栈顺序)-->
<interceptor-stack name="myInterceptorStack">
<interceptor-ref name="myInterceptor"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
<action name="index" class="com.example.project.HelloWorldAction">
<result name="success">/hello.jsp</result>
<!--配置此action使用的拦截器(栈)-->
<interceptor-ref name="myInterceptorStack"></interceptor-ref>
</action>

default-interceptor-ref

default-interceptor-ref标签用于指定默认拦截器(栈)

global-results

global-results标签用于全局结果集设置,例如:

1
2
3
4
<!--配置一个全局结果集-->
<global-results>
<result name="default-result">/login.jsp</result>
</global-results>

创建action

action响应页面展示

本文将创建 HelloWorldAciton 类继承于 ActionSupport,实现其 execute() 方法(类似 Servlet 接口中 service() 方法)

  1. 创建 HelloWorldAciton 类,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    public class HelloWorldAction extends ActionSupport {

    @Override
    public String execute() throws Exception {
    System.out.println("HelloWorldAction execute.");
    return ActionSupport.SUCCESS;
    }
    }
  2. 同时,在 struts.xml 文件中写入如下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
    "http://struts.apache.org/dtds/struts-2.5.dtd">
    <struts>
    <package name="example-package" namespace="/" extends="struts-default">
    <action name="index" class="com.example.project.HelloWorldAction">
    <result name="success">/hello.jsp</result>
    </action>
    </package>
    </struts>
  3. 随后在web文件夹中创建名为 hello.jsp 的JSP页面。

至此,您已成功创建action并投入运行。并且action中并无显示内容相关代码,仅通过返回字符串决定响应页面,这便是MVC优势所在。

action读取表单参数

接下来,继续实现表单提交功能:

  1. 实现Model,此处使用User类作为JavaBean,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class User {
    private String name;
    private String password;
    public User() {
    }
    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;
    }
    }
  2. action 中实现 ModelDriven 接口绑定Model,此处Model为 User

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class HelloWorldAction extends ActionSupport implements ModelDriven<User> {
    //此处一定要先实例化,否则Struts2框架调用getModel方法的返回值为null
    private User user = new User();

    @Override
    public String execute() throws Exception {
    return ActionSupport.SUCCESS;
    }

    @Override
    public User getModel() {
    return user;
    }
    }
  3. login.jsp 页面中实现表单提交功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <form action="index.action" method="post">
    <label>
    用户名:
    <input type="text" name="name">
    </label>
    <label>
    密码:
    <input type="password" name="password">
    </label>
    <input type="submit">
    </form>

现在,访问 login.jsp 即可实现表单提交功能。

action内容验证

最后实现表单内容验证功能:

  1. 实现 ActionSupport 基类的 validate() 方法

    1
    2
    3
    4
    5
    6
    @Override
    public void validate() {
    if (null == user.getName() || user.getName().equals(""))
    // 当addFieldError之后action调用返回值自动变更为 `input`
    this.addFieldError("username", "用户名不能为空");
    }
  2. 配置 strtuts.xml 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
    "http://struts.apache.org/dtds/struts-2.5.dtd">
    <struts>
    <package name="example-package" namespace="/" extends="struts-default">
    <action name="index" class="com.example.project.HelloWorldAction">
    <result name="success">/hello.jsp</result>
    <result name="input">/login.jsp</result>
    </action>
    </package>
    </struts>
  3. 修改login.jsp 页面代码,实现错误信息读取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <form action="index.action" method="post">
    <s:fielderror fieldName="username"/>
    <label>
    用户名:
    <input type="text" name="name">
    </label>
    <label>
    密码:
    <input type="password" name="password">
    </label>
    <input type="submit">
    </form>

action访问Servlet API

访问Servlet API,只需实现 ServletContextAwareServletRequestAwareServletResponseAware 接口即可。
以下代码实现 ServletRequestAware 接口的 setServletRequest 方法设置request属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HelloWorldAction extends ActionSupport implements ServletRequestAware {

private HttpServletRequest request;

@Override
public String execute() throws Exception {
request.setAttribute("path", "HelloWorld");
return Action.SUCCESS;
}

@Override
public void setServletRequest(HttpServletRequest httpServletRequest) {
this.request = httpServletRequest;
}
}

结合 struts.xml 文件中 result 标签的OGNL表达式内容,即可实现动态跳转。

创建interceptor

本文将创建 MyInterceptor 类继承于 Interceptor,实现其 intercept() 方法(类似 Filter 接口中 doFilter() 方法)

  1. 创建 MyInterceptor 类,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class MyInterceptor extends Interceptor {
    @Override
    public void destroy() {
    }
    @Override
    public void init() {
    }
    @Override
    public String intercept(ActionInvocation actionInvocation) throws Exception {
    System.out.println("进入拦截器");
    String returnName = actionInvocation.invoke();
    System.out.println("走出拦截器");
    return returnName;
    }
    }
  2. 同时,在 struts.xml 文件中写入如下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
    "http://struts.apache.org/dtds/struts-2.5.dtd">
    <struts>
    <package name="example-package" namespace="/" extends="struts-default">
    <interceptors>
    <interceptor name="myInterceptor" class="com.example.project.MyInterceptor"></interceptor>
    <interceptor-stack name="myInterceptorStack">
    <interceptor-ref name="myInterceptor"></interceptor-ref>
    <interceptor-ref name="defaultStack"></interceptor-ref>
    </interceptor-stack>
    </interceptors>
    <action name="index" class="com.example.project.HelloWorldAction">
    <result name="success">/hello.jsp</result>
    <interceptor-ref name="myInterceptorStack"></interceptor-ref>
    </action>
    </package>
    </struts>

另有针对性极强的方法拦截器,实现该拦截器需继承 MethodFilterInterceptor 接口的 doIntercept() 方法,不再累述。

注意事项

Q: 配置 web.xml 中的 welcome-file 为action对应URL时并未生效?
A: web.xml 配置文件由servlet容器(tomcat)读取,tomcat 启动 main 函数生成 Bootstrap 实例,随即执行 init() 初始化,并调用 start() 方法。
init() 方法里初始化classloader,由此创建 Catalina 实例。Bootstrapstart() 方法调用 Catalina 实例相应 load(args) 方法创建 Server 对象。
Service 对象调用 initstart 方法启动 Container (即 EngineHostContextWrapper 接口的各个实现类)。
其中 Context 接口的实现类 StandardContextbindThread() 后触发CONFIGURE_START_EVENT事件, ContextConfig 接收事件后调用 configureStart() 方法。
之后调用 webConfig() 方法,进入 WebXmlParser 实例的 parseWebXml() 方法,通过 DigesterWebRuleSet 实例解析 web.xml ,再进入 configureContext(webXml) 方法将 welcome-file 标签内容通过 StandardContext 实例的 addWelcomeFile(string) 方法添加其中。
在URL请求时按照流程 EndPoint -> Processor -> CoyoteAdapter -> Mapper 进行处理,Mapper根据以下匹配次序进行URL和wrapper容器(容器中包含Servlet,即Servlet)的匹配,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
private final void internalMapWrapper(ContextVersion contextVersion, CharChunk path, MappingData mappingData) throws IOException {
int pathOffset = path.getOffset();
int pathEnd = path.getEnd();
boolean noServletPath = false;
int length = contextVersion.path.length();
if (length == (pathEnd - pathOffset)) {
noServletPath = true;
}
int servletPath = pathOffset + length;
path.setOffset(servletPath);

// Rule 1 -- Exact Match
MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
internalMapExactWrapper(exactWrappers, path, mappingData);

// Rule 2 -- Prefix Match
boolean checkJspWelcomeFiles = false;
MappedWrapper[] wildcardWrappers = contextVersion.wildcardWrappers;
if (mappingData.wrapper == null) {
internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting, path, mappingData);
if (mappingData.wrapper != null && mappingData.jspWildCard) {
char[] buf = path.getBuffer();
if (buf[pathEnd - 1] == '/') {
/*
* Path ending in '/' was mapped to JSP servlet based on
* wildcard match (e.g., as specified in url-pattern of a
* jsp-property-group.
* Force the context's welcome files, which are interpreted
* as JSP files (since they match the url-pattern), to be
* considered. See Bugzilla 27664.
*/
mappingData.wrapper = null;
checkJspWelcomeFiles = true;
} else {
// See Bugzilla 27704
mappingData.wrapperPath.setChars(buf, path.getStart(), path.getLength());
mappingData.pathInfo.recycle();
}
}
}

if(mappingData.wrapper == null && noServletPath && contextVersion.object.getMapperContextRootRedirectEnabled()) {
// The path is empty, redirect to "/"
path.append('/');
pathEnd = path.getEnd();
mappingData.redirectPath.setChars(path.getBuffer(), pathOffset, pathEnd - pathOffset);
path.setEnd(pathEnd - 1);
return;
}

// Rule 3 -- Extension Match
MappedWrapper[] extensionWrappers = contextVersion.extensionWrappers;
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
internalMapExtensionWrapper(extensionWrappers, path, mappingData, true);
}

// Rule 4 -- Welcome resources processing for servlets
if (mappingData.wrapper == null) {
boolean checkWelcomeFiles = checkJspWelcomeFiles;
if (!checkWelcomeFiles) {
char[] buf = path.getBuffer();
checkWelcomeFiles = (buf[pathEnd - 1] == '/');
}
if (checkWelcomeFiles) {
for (int i = 0; (i < contextVersion.welcomeResources.length) && (mappingData.wrapper == null); i++) {
path.setOffset(pathOffset);
path.setEnd(pathEnd);
path.append(contextVersion.welcomeResources[i], 0, contextVersion.welcomeResources[i].length());
path.setOffset(servletPath);

// Rule 4a -- Welcome resources processing for exact macth
internalMapExactWrapper(exactWrappers, path, mappingData);

// Rule 4b -- Welcome resources processing for prefix match
if (mappingData.wrapper == null) {
internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting, path, mappingData);
}

// Rule 4c -- Welcome resources processing for physical folder
if (mappingData.wrapper == null && contextVersion.resources != null) {
String pathStr = path.toString();
WebResource file = contextVersion.resources.getResource(pathStr);
if (file != null && file.isFile()) {
internalMapExtensionWrapper(extensionWrappers, path, mappingData, true);
if (mappingData.wrapper == null && contextVersion.defaultWrapper != null) {
mappingData.wrapper = contextVersion.defaultWrapper.object;
mappingData.requestPath.setChars(path.getBuffer(), path.getStart(), path.getLength());
mappingData.wrapperPath.setChars(path.getBuffer(), path.getStart(), path.getLength());
mappingData.requestPath.setString(pathStr);
mappingData.wrapperPath.setString(pathStr);
}
}
}
}
path.setOffset(servletPath);
path.setEnd(pathEnd);
}
}

/* welcome file processing - take 2
* Now that we have looked for welcome files with a physical
* backing, now look for an extension mapping listed
* but may not have a physical backing to it. This is for
* the case of index.jsf, index.do, etc.
* A watered down version of rule 4
*/
if (mappingData.wrapper == null) {
boolean checkWelcomeFiles = checkJspWelcomeFiles;
if (!checkWelcomeFiles) {
char[] buf = path.getBuffer();
checkWelcomeFiles = (buf[pathEnd - 1] == '/');
}
if (checkWelcomeFiles) {
for (int i = 0; (i < contextVersion.welcomeResources.length) && (mappingData.wrapper == null); i++) {
path.setOffset(pathOffset);
path.setEnd(pathEnd);
path.append(contextVersion.welcomeResources[i], 0, contextVersion.welcomeResources[i].length());
path.setOffset(servletPath);
internalMapExtensionWrapper(extensionWrappers, path, mappingData, false);
}
path.setOffset(servletPath);
path.setEnd(pathEnd);
}
}

// Rule 7 -- Default servlet
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
if (contextVersion.defaultWrapper != null) {
mappingData.wrapper = contextVersion.defaultWrapper.object;
mappingData.requestPath.setChars(path.getBuffer(), path.getStart(), path.getLength());
mappingData.wrapperPath.setChars(path.getBuffer(), path.getStart(), path.getLength());
mappingData.matchType = MappingMatch.DEFAULT;
}
// Redirection to a folder
char[] buf = path.getBuffer();
if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {
String pathStr = path.toString();
WebResource file;
// Handle context root
if (pathStr.length() == 0) {
file = contextVersion.resources.getResource("/");
} else {
file = contextVersion.resources.getResource(pathStr);
}
if (file != null && file.isDirectory() && contextVersion.object.getMapperDirectoryRedirectEnabled()) {
// Note: this mutates the path: do not do any processing
// after this (since we set the redirectPath, there
// shouldn't be any)
path.setOffset(pathOffset);
path.append('/');
mappingData.redirectPath.setChars(path.getBuffer(), path.getStart(), path.getLength());
} else {
mappingData.requestPath.setString(pathStr);
mappingData.wrapperPath.setString(pathStr);
}
}
}
path.setOffset(pathOffset);
path.setEnd(pathEnd);
}

  1. 精确匹配(Exact Match)
  2. 前缀匹配(Prefix Match)
  3. 后缀匹配(Extension Match)
  4. 加入欢迎页面地址匹配(Welcome resources processing for servlets)
    4.1. 欢迎页面精确匹配(Welcome resources processing for exact macth)
    4.2. 欢迎页面前缀匹配(Welcome resources processing for prefix match)
    4.3. 欢迎页面静态文件匹配(Welcome resources processing for physical folder)
    4.4. 欢迎页面后缀匹配(welcome file processing - take 2)
  5. 默认Servlet路径“/”匹配(Default servlet)

进入 Mapper 时由于首次启动访问主页URL为 http://host:port/project/ 以“/”结尾,故步骤1、2、3均匹配失败。
进入步骤4,请求URL变为 http://host:port/project/*.action
然而 wrapper容器 仅支持查找 Servlet ,故步骤4.1、4.2均失败,由于未创建文件 *.action 故步骤4.3依旧失败。
wrapper容器 仅支持查找 Servlet ,同理步骤4.4也失败。
URL重新退化为 http://host:port/project/ 进入步骤7成功匹配,传给过滤器(filter)处理,进入Struts2框架。
Struts2框架无法匹配此URL对应action,故主页设置未生效。

总结

至此,Struts2框架基本使用方法掌握。

CC许可协议署名非商业性使用相同方式共享
本文采用 CC BY-NC-SA 3.0 Unported 协议进行许可