SSH框架-初识Struts2
概述
最初的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运行流程图:
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工作原理如下所示:
Struts2原理
Struts2基于过滤器(filter)实现,其工作原理如下:
图中名词解释:
- 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使用
环境配置
- 创建Java Web项目并导入与Struts2相关的Jar开发包。开发包下载地址
配置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>
- 1
在src文件夹内创建struts.xml文件,该文件用于配置全局属性、Interceptor以及action
- 1
2
3
4
5
6
7
- <?xml version="1.0" encoding="UTF-8"?>
<struts>
</struts>
- 1
struts.xml
constant
constant标签用于配置全局属性,如:
|
|
可也置于struts.properties文件中,以键值对形式保存
|
|
更多属性请查阅Struts2官网
package
package标签表示独立模块,每个package都有独立的interceptor、action定义
其属性abstract用于创建抽象package,namespace用于路径匹配(相当于文件夹路径),extends用于继承配置,例如
|
|
关于URL中 namespace
路径的访问并非严格匹配,而是层级向上查找。例如:
当仅有单个package且其 namespace=path1
时,对于路径:
- http://host:port/project/path1/example.action
- http://host:port/project/path1/path2/example.action
- http://host:port/project/path1/path2/path3/example.action
均可访问到example.action对应页面。
action
action标签用于定义URL名字对应的action类,例如
|
|
name属性支持正则匹配,因此可对其作些文章:
|
|
设定method属性值,可指定调用对应名称的方法,如:
|
|
result
result标签用于指定action方法返回值对应跳转页面(跳转默认采用forward方式,即type=”dispatcher”),例如
|
|
因为struts.xml支持OGNL表达式,故可取request、session、application等属性值:
|
|
以下为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页面友好设计,例如:
|
|
interceptor
interceptor标签用于拦截action请求响应,例如
|
|
default-interceptor-ref
default-interceptor-ref标签用于指定默认拦截器(栈)
global-results
global-results标签用于全局结果集设置,例如:
|
|
创建action
action响应页面展示
本文将创建 HelloWorldAciton
类继承于 ActionSupport
,实现其 execute()
方法(类似 Servlet
接口中 service()
方法)
创建
HelloWorldAciton
类,代码如下- 1
2
3
4
5
6
7
8
- public class HelloWorldAction extends ActionSupport {
public String execute() throws Exception {
System.out.println("HelloWorldAction execute.");
return ActionSupport.SUCCESS;
}
}
- 1
同时,在
struts.xml
文件中写入如下内容:- 1
2
3
4
5
6
7
8
9
10
11
- <?xml version="1.0" encoding="UTF-8"?>
<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>
- 1
随后在web文件夹中创建名为
hello.jsp
的JSP页面。
至此,您已成功创建action并投入运行。并且action中并无显示内容相关代码,仅通过返回字符串决定响应页面,这便是MVC优势所在。
action读取表单参数
接下来,继续实现表单提交功能:
实现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;
}
}
- 1
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();
public String execute() throws Exception {
return ActionSupport.SUCCESS;
}
public User getModel() {
return user;
}
}
- 1
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>
- 1
现在,访问 login.jsp
即可实现表单提交功能。
action内容验证
最后实现表单内容验证功能:
实现
ActionSupport
基类的validate()
方法- 1
2
3
4
5
6
public void validate() {
if (null == user.getName() || user.getName().equals(""))
// 当addFieldError之后action调用返回值自动变更为 `input`
this.addFieldError("username", "用户名不能为空");
}
- 1
配置
strtuts.xml
文件- 1
2
3
4
5
6
7
8
9
10
11
12
- <?xml version="1.0" encoding="UTF-8"?>
<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>
- 1
修改
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>
- 1
action访问Servlet API
访问Servlet API,只需实现 ServletContextAware
、 ServletRequestAware
、 ServletResponseAware
接口即可。
以下代码实现 ServletRequestAware
接口的 setServletRequest
方法设置request属性:
|
|
结合 struts.xml
文件中 result
标签的OGNL表达式内容,即可实现动态跳转。
创建interceptor
本文将创建 MyInterceptor
类继承于 Interceptor
,实现其 intercept()
方法(类似 Filter
接口中 doFilter()
方法)
创建
MyInterceptor
类,代码如下- 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- public class MyInterceptor extends Interceptor {
public void destroy() {
}
public void init() {
}
public String intercept(ActionInvocation actionInvocation) throws Exception {
System.out.println("进入拦截器");
String returnName = actionInvocation.invoke();
System.out.println("走出拦截器");
return returnName;
}
}
- 1
同时,在
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"?>
<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>
- 1
另有针对性极强的方法拦截器,实现该拦截器需继承 MethodFilterInterceptor
接口的 doIntercept()
方法,不再累述。
注意事项
Q: 配置 web.xml
中的 welcome-file
为action对应URL时并未生效?
A: web.xml
配置文件由servlet容器(tomcat)读取,tomcat
启动 main
函数生成 Bootstrap
实例,随即执行 init()
初始化,并调用 start()
方法。init()
方法里初始化classloader,由此创建 Catalina
实例。Bootstrap
的 start()
方法调用 Catalina
实例相应 load(args)
方法创建 Server
对象。Service
对象调用 init
和 start
方法启动 Container
(即 Engine
、 Host
、 Context
、 Wrapper
接口的各个实现类)。
其中 Context
接口的实现类 StandardContext
在 bindThread()
后触发CONFIGURE_START_EVENT事件, ContextConfig
接收事件后调用 configureStart()
方法。
之后调用 webConfig()
方法,进入 WebXmlParser
实例的 parseWebXml()
方法,通过 Digester
和 WebRuleSet
实例解析 web.xml
,再进入 configureContext(webXml)
方法将 welcome-file
标签内容通过 StandardContext
实例的 addWelcomeFile(string)
方法添加其中。
在URL请求时按照流程 EndPoint -> Processor -> CoyoteAdapter -> Mapper
进行处理,Mapper根据以下匹配次序进行URL和wrapper容器(容器中包含Servlet,即Servlet)的匹配,代码如下:
|
|
- 精确匹配(Exact Match)
- 前缀匹配(Prefix Match)
- 后缀匹配(Extension Match)
- 加入欢迎页面地址匹配(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) - 默认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 BY-NC-SA 3.0 Unported 协议进行许可