SSH Chapter 11 Struts2 拦截器
本章目标 :
- 掌握Struts 2架构
- 掌握Struts 2拦截器
- 掌握Struts 2框架的文件上传和下载
1 . Struts 2 架构分析
2006年,WebWork与Struts这两个优秀的JavaEE Web框架(Web Framework)的团体,决定合作共同开发一个新的,整合了WebWork与Struts优点,并且更加优雅、扩展性更强的框架,命名为“Struts2”,原Struts的1.x版本产品称为”Struts1”。
Struts2框架由两部分组成:XWork2和Struts2。XWork2提供了很多核心功能:IoC容器,强大的表达式语言(OGNL),数据类型转换,验证和可插入的配置。XWork框架的核心概念包括:aciton、拦截器和result。Struts2扩展了这些核心概念的基础实现 , 用于支持Web应用程序的开发
在Struts2的文档中提供了如图所示的体系结构图:
- Servlet Filters(橙色):过滤链,所有的请求都要经过Filter处理。
- Struts Core(浅蓝色):Struts2的核心部分,Struts2已经做好的功能,开发中不需要动它们。
- Interceptors(浅绿色):Struts2的拦截器。Struts2提供了很多默认的拦截器,帮助开发者完成绝大部分工作。开发者也可以自定义拦截器,来实现具体的功能。
- User Created(浅黄色):这一部分需要由开发人员完成,包括struts.xml、Action、Template等。
说明:
不同版本的Struts 2的核心控制器有所不同 , 为了便于描述 , 下面有关核心控制器的名称均采用FilterDispatcher来进行描述 .
需要特别指出的是 , FilterDispatcher在版本2.1.3后被StrutsPrepareAndExecuteFilter替代 .
1.1 Strut 2各模块的说明
Struts2框架中的各个模块各自是做什么的?有什么样的功能?处于什么样的地位?
下面跟着系统架构图上的箭头一个一个地来查看
- FilterDispatcher:是整个Struts2的调度中心,根据ActionMapper的结果来决定是否处理请求,如果ActionMappr指出该URL应该被Struts处理,那么它将会执行Action处理,并停止过滤器链上还没有被执行的过滤器。
- ActionMapper:提供了HTTP请求与Action执行之间的映射,简单地说,ActionMapper会判断这个请求是否应该被Struts2处理,如果需要Struts2处理,ActionMapper会返回一个对象来描述请求对应的ActionInvocation的信息
- ActionProxy:是一个特别的中间层,位于Action和xwork之间,使得我们在将来有机会引入更多额外实现方式,比如通过WebService来实现等
- ConfigurationManager:是xwork配置的管理中心,可以把它看作struts.xml这个配置文件在内存中的对应struts.xml(是Struts2的应用配置文件),负责诸如URL与Action之间映射的配置,以及执行后页面跳转的Result配置等
- ActionInvocation:真正调用并执行Action,它拥有一个Action实例和这个Action所依赖的拦截器实例 . ActionInvocation会执行这些拦截器、Action以及相应的Result.
- Interceptor:拦截器是一些无状态的类,拦截器可以自动拦截Action,它们给开发者提供了在Action运行之前或Result运行之后执行一些功能代码的机会,类似于我们熟悉的javax.servlet.Filter.
- Action:动作类是Struts2中的动作执行单元。用来处理用户请求,并封装业务所需要的数据
- Result:Result就是不同视图类型的抽象封装模型,不同的视图类型会对应不同的Result实现,Struts2中支持多种视图类型,比如JSP,FreeeMarker等
- Template:各种视图类型的页面模板,例如,JSP就是一种模板页面技术
- Tag Subsystem:Struts2的标签库,它抽象了三种不同的视图技术:JSP,Velocity,FreeMarker,可以在不同的视图技术中,几乎没有差别地使用这些标签。
1.2 Struts 2 体系结构
- 当Web容器收到一个请求时,它将请求传递给一个标准的过滤器链,其中包括ActionContentCleanUp过滤器和其他过滤器(如集成SiteMess的插件),这是非常有用的技术。接下来,需要调用FilterDispatcher,它将调用ActionMapper,来确定请求调用哪个Action,ActionMapper返回一个收集了Action详细信息的ActionMapping对象
- 接下来,FilterDispatcher将控制权委派给ActionProxy,ActionProxy调用配置管理器(Configuration Manager)从配置文件中读取配置信息,然后创建ActionInvocation对象,实际上,ActionInvocation的处理过程就是Struts2处理请求的过程
- 一旦Action执行返回结果字符串,ActionInvocation负责查找结果字符串对应的Result,然后执行这个Result。通常情况下Result会调用一些模板(JSP等)来呈现页面
- 之后,拦截器会被再次执行(顺序与Action执行前相反),最后响应被返回给在web.xml中配置的那些过滤器(FilterDispatcher等)。
1.3 Struts 2的运行流程
众所周知,Struts框架是基于MVC模式的,基于MVC模式框架核心就是控制器对所有请求进行统一处理。
传统的JSP页面通过GET或POST方法向服务器端的JSP页面提交数据。
采用Struts2框架后,不再提交给服务器端的JSP页面,框架会根据web.xml配置文件和struts.xml配置文件的配置内容,将数据提交给相应的ActionSupport类处理,并返回结果。根据返回的结果(Reuslt)和struts.xml文件中的配置内容(result标签对应的页面),将响应的页面返回给客户端
核心控制器
StrutsPrepareAndExecuteFilter控制器是Struts2框架的核心控制器,该控制器负责拦截所有的用户请求,用户请求到达时,该控制器或过滤用户的请求,如果用户请求以action结尾,改请求将被交给Struts2框架来处理。
当Struts2框架获得了用户请求后,根据请求的名字决定调用那部分业务逻辑组建,例如,对应login请求,Struts2调用login所对应的LoginAction业务来处理该请求。(对应关系请查看struts.xml配置文件的格式以及各标签代表的含义)
业务控制器
Action就是Struts2的业务逻辑控制器,复制处理客户端请求并将结果输出给客户端。对开发人员来说,使用Struts2框架,主要的编码工作就是编写Action类,Struts2并不要求编写的Action类一定要实现Action接口,可以编写一个普通的Java类作为Action类,只要给类含有一个返回字符串的无参的public方法即可。
运行流程解析如下:
- 客户端向Servlet容器(如Tomcat)提交一个请求
- 请求经过一系列过滤器(如ActionContextCleanUp过滤器等); 备注:从struts2.1.3开始ActionContextCleanUp 和 FilterDispatcher过滤器,已经不建议使用了。将使用StrutsPrepareFilter和StrutsExecuteFilter拦截器替代
- 核心控制器被调用,询问ActionMapper来决定请求是否需要调用某个Action
- 如果ActionMapper决定需要调用某个Action,核心控制器把控制权委派给ActionProxy (备注:JSP请求无需调用Action)
- ActionProxy通过Configuration Manager询问框架的配置文件(struts.xml),找到需调用的Action类
- ActionProxy创建一个ActionInvocation的实例
- ActionInvocation负责调用Action,在此之前会依次调用所有配置的拦截器
- Action执行完毕,ActionInvocation负责根据结果字符串在struts.xml的配置中找到对应的返回结果
- 根据结果(Result)找到页面后,在页面上(有很多Struts2提供的模板),可以通过Struts2自带的标签库来访问需要的数据,并生成最终页面注意:这时还没有给客户端应答,只是生成了页面
- 最后,ActionInvocation对象倒序执行拦截器
- ActionInvocation对象执行完毕后,已经得到响应对象(HttpServletResponse)了,最后按与过滤器(Filter)配置定义相反的顺序依次经过过滤器,向客户端展示出响应的结果
2 . Struts 2 的拦截器
2.1 为什么需要使用拦截器
任何优秀的MVC框架都会提供一些通用的操作 , 如请求数据的封装 , 类型转换 , 数据校验 , 解析上传的文件,防止表单的多次提交等 . 早期的MVC框架将这些操作统一封装在核心控制器中 , 但是这些通用的操作并不是所有的请求都需要实现,因此导致了框架的灵活性不足,可扩展性低 。
Struts2将它的核心功能放到拦截器中实现,而不是集中在核心控制器中实现 . 把大部分控制器需要完成的工作按功能分开定义,每个拦器器完成一个功能 , 而完成这些这些功能的拦截器可以自由选择、灵活组合 . 需要哪些拦截器,只需要在
struts.xml
指定即可,从而增强了框架的灵活性。
拦截器方法在action执行之前或之后自动执行,从而将通用的操作动态地插入到Action的前后,有利于系统的解耦 . 这种功能的实现类似于我们自己组装的计算机 , 变成了可插拔式 , 需要某个功能就"插入"
一个拦截器 , 不需要某个功能就"拔出"
一个拦截器 . 可以任意的组合Action提供的附加功能 , 而不需要修改Action代码.
如果有一批拦截器经常固定在一起使用,可以将这些执行小粒度功能的拦截器定义成大粒度的拦截器栈(根据不同的应用需求而定义拦截器组合)。从结构上看,拦截器相当于多个拦截器的组合 . 从功能上看,拦截器栈也是拦截器,同样可以和其他拦截器或拦截器一起组合更大粒度的拦截器栈。
通过组合不同的拦截器,我们能够以自己需要的方式来组合Struts2框架的各种功能 ; 通过扩展自己的拦截器 , 我们可以"无限"
扩展 Struts2 框架 .
2.2 拦截器的工作原理
拦截器围绕着Action 和 Result的执行而执行。拦截器的工作方式如下图所示 , 从图中可以看出 , Struts2拦截器的实现原理和Servlet Filter 的实现原理差不多,以链式执行,对真正要执行的方法进行拦截 .
首先执行Action配置的拦截器,在Action和Result执行之后,拦截器再一次执行,与之前调用相反的顺序,在此链式的执行过程中,任何一个拦截器都可以直接返回,从而终止余下的拦截器、Action及Result的执行。
当ActionInvocation的invoke()方法调用时,开始执行Action配置的第一个拦截器 , 拦截器做出相应的处理后会再次调用ActionInvocation的invoke()方法 , ActionInvocation对象负责跟踪执行过程到状态,并把控制权交个合适的拦截器。
ActionInvocation通过调用拦截器的 intercept()方法将控制转交给拦截器。因此 , 拦截器的执行过程可以看作是一个递归的过程,后续拦截器继续执行,直到最后一个拦截器,invoke()方法会执行Action.
拦截器有一个三阶段、有条件的执行周期 , 如下所示:
- 首先做一些Action执行前的预处理。拦截器可以准备、过滤、改变或者操作任何可以访问的数据,包括Action
- 调用ActionInvocation的invoke()方法将控制权转交给后续的拦截器或者返回结果字符终止执行,如果拦截器决定请求的处理不应该继续 , 可以不调用invoke()方法 , 而是直接返回一个控制字符串 . 通过这种方式,可以停止后续的执行,并且决定将哪个结果呈现给客户端。
- 做一些Action执行后的处理。此时拦截器可以改变访问的对象和数据,只是此时框架已经选择了一个结果呈现给客户端了
一句话总结: 拦截器在Action对象创建之后 , 框架调用Action方法之前执行
下面通过示例代码来体会拦截器的三个阶段 , 如示例1所示:
示例1
MyTimerInterceptor.java
代码如下:
package cn.strutsdemo.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
/**
* 定义拦截器
*/
public class MyTimerInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception {
//1.执行Action之前的工作:获取开始执行时间
long startTime = System.currentTimeMillis();
System.out.println("执行Action之前的工作,开始时间:"+startTime);
//2.执行后续拦截器或Action
/*if(true){
return "success";//可以随时控制后续拦截器和acion
}*/
String result = actionInvocation.invoke();
//1.执行Action之后的工作:计算并输出执行时间
long endTime = System.currentTimeMillis();
System.out.println("执行Action之前的工作,结束时间:" + endTime);
System.out.println("总共用时:"+(endTime-startTime));
//返回结果字符串
return result;
}
}
说明:
MyTimerInterceptor
拦 还行截器记录动作执行所花费的时间 , 代码很简单 . intercept()方法是拦截器执行的入口方法 , 需要注意的是它接受的是ActionInvocation
实例 .
当
intercept
方法被调用时 , 拦截器开始记录时间(也就是进行预处理的工作) , 接着MyTimerInterceptor
拦截器调用ActionInvocation
实例的invoke()
方法 , 将控制转交给剩余的拦截器和Action , 因为记录执行没有理由终止执行 , 所以MyTimerInterceptor
拦截器总是调用invoke()
方法 .
在调用invoke()
方法后 , MyTimerInterceptor
拦截器等待这个方法的返回值 . 虽然这个结果字符串告诉MyTimerInterceptor
拦截器哪个结果会被呈现 , 但并未之处Action是否执行(可能剩余的拦截器终止了执行操作) . 无论Action是否执行 , invoke()
方法返回时 , 就表明某个结果已经被呈现了(响应页面已经生成完毕).
获得结果字符串之后 ,
MyTimerInterceptor
拦截器记录了执行的用时 , 并在控制台进行了输出 . 此时拦截器可以使用结果字符串做一些操作 , 但是在这里不能停止或者改变响应 . 对于MyTimerInterceptor
拦截器而言 , 它不关心结果 , 因此它不查看返回的结果字符串.
MyTimerInterceptor
拦截器执行到最后 , 返回了从invoke()
方法获得的结果字符串 , 从而使递归又回到了拦截器链 , 使前面的拦截器继续执行它们的后续处理工作
2.3 拦截器的配置
配置拦截需要经过两个步骤:
- 通过
<interceptor />
元素来定义拦截器
- 通过
<interceptor - ref />
元素来使用拦截器
定义MyTimerAction.java
, 代码如下:
package cn.strutsdemo.action;
import com.opensymphony.xwork2.ActionSupport;
public class MyTimerAction extends ActionSupport {
@Override
public String execute() throws Exception {
System.out.println("执行Action业务");
return SUCCESS;
}
}
使用拦截器的struts.xml
配置文件 , 内容如示例2所示:
<?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>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<!--配置拦截器-->
<interceptors>
<!--定义拦截器-->
<interceptor name="myTimer"
class="cn.strutsdemo.interceptor.MyTimerInterceptor"/>
</interceptors>
<action name="action"
class="cn.strutsdemo.action.MyTimerAction">
<result>index.jsp</result>
<!--引用拦截器-->
<interceptor-ref name="myTimer"/>
<interceptor-ref name="defaultStack"/>
</action>
</package>
</struts>
说明:
在struts.xml
文件中 , 首先在<interceptors>
元素中使用<interceptor>
元素来定义拦截器 , <interceptor>
元素的name
属性和class
属性是必须填写的 , 前者指定拦截器的名字 , 后者指定拦截器的全限定类名 . 然后在<action>
元素中使用<interceptor-ref>
子元素指定引用的拦截器 .
如果除了希望调用自己编写的拦截器外 , 还希望调用
Struts2
框架定义的默认拦截器 , 就需要将默认拦截器一并添加到<action>
元素中 , 有关 Struts 2 默认的拦截器的内容将在后面介绍.
示例2实现了一个简单的拦截器配置 , 运行程序后在控制台将会输出Action的执行用时 , 如下所示:
执行Action之前的工作,开始时间:1571055411023
执行Action业务
执行Action之前的工作,结束时间:1571055411773
总共用时:750
在Struts 2体系结构中可以包含多个拦截器 , 配置过程会相对复杂 , 而拦截器的详细配置过程如图示例3所示:
示例3:
<package name="packName" extends="struts-default" namespace="/manage">
<interceptors>
<!-- 定义拦截器 -->
<interceptor name="interceptorName" class="interceptorClass" />
<!-- 定义拦截器栈 -->
<interceptor-stack name="interceptorStackName">
<!--指定引用的拦截器-->
<interceptor-ref name="interceptorName|interceptorStackName" />
</interceptor-stack>
</interceptors>
<!--定义默认的拦截器引用-->
<default-interceptor-ref name="interceptorName|interceptorStackName" />
<action name="actionName" class="actionClass">
<!—- 为Action指定拦截器引用 -->
<interceptor-ref name="interceptorName|interceptorStackName"/>
<!--省略其他配置-->
</action>
</package>
修改struts.xml
, 使用拦截器栈 ,内容如下:
<?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>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<!--配置拦截器-->
<interceptors>
<!--定义拦截器-->
<interceptor name="myTimer"
class="cn.strutsdemo.interceptor.MyTimerInterceptor"/>
<!--定义拦截器栈-->
<interceptor-stack name="myTimerStack">
<interceptor-ref name="myTimer"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>
<action name="action"
class="cn.strutsdemo.action.MyTimerAction">
<result>index.jsp</result>
<!--此处引用拦截器栈-->
<interceptor-ref name="myTimerStack"/>
<!--<interceptor-ref name="myTimer"/>
<interceptor-ref name="defaultStack"/>-->
</action>
</package>
</struts>
如果想要把多个拦截器组成一个拦截器栈,就需要在 interceptors
元素中使用interceptor-stack
元素定义拦截器栈 , 其中name
属性指定拦截器栈的名称 , 依然使用intercept-ref
元素指定引用的拦截器.
解释:引用拦截器时,Struts2并不区分拦截器和拦截器栈,因此在定义拦截器时,可以引用其他的拦截器栈。
如果配置文件大多数Action都引用相同的拦截器,可以使用默认的拦截器引用。<default-interceptor-ref>
元素用来定义默认的拦截器引用 , 其name
属性指定引用的拦截器或拦截器栈的名称。
如下所示:
可以继续简化struts.xml
, 内容如下:
<?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>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<!--配置拦截器-->
<interceptors>
<!--定义拦截器-->
<interceptor name="myTimer"
class="cn.strutsdemo.interceptor.MyTimerInterceptor"/>
<!--定义拦截器栈-->
<interceptor-stack name="myTimerStack">
<interceptor-ref name="myTimer"/>
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
</interceptors>
<!--定义默认的拦截器引用-->
<default-interceptor-ref name="myTimerStack"/>
<action name="action"
class="cn.strutsdemo.action.MyTimerAction">
<result>index.jsp</result>
<!--此处引用拦截器栈-->
<!--<interceptor-ref name="myTimerStack"/>-->
<!--<interceptor-ref name="myTimer"/>
<interceptor-ref name="defaultStack"/>-->
</action>
</package>
</struts>
2.4 Struts 2 内置拦截器
在Struts2框架中,内置了很多拦截器供开发人员使用。
1. params 拦截器
提供了框架必不可少的功能,将请求中的数据设置到Action的属性上。负责将请求参数设置成action属性。
2. staticParams 拦截器
staticParams拦截器是将在配置文件中通过
<action>
元素的子元素<param>
设置的参数设置到对于到Acton属性中。
3. servletConfig 拦截器
servletConfig拦截器提供了一种将源于 ServletAPI的各种对象注入 Action 当中的简洁方法 . Action必须实现相对应的接口,此拦截器才能将对应的Servlet对象注入Action中 .
下列表格中的 接口可以由Action实现,用来取得 Servlet API的不同对象:
接口 | 作用 |
---|---|
ServletContextAware | 设置 ServletContext |
ServletRequestAware | 设置 HttpServletRequest |
ServletResponseAware | 设置 ServletResponse |
ParameterAware | 设置 Map 类型的请求参数 |
RequestAware | 设置 Map 类型的请求 |
SessionAware | 设置 Map 类型的会话 |
ApplicationAware | 设置 Map 类型的应用程序作用域对象 |
解释:为了降低 Action 与 Servlet API 之间的耦合 ,在实际的开发中要尽量减少或者避免在Action中直接访问 Servlet API
4. fileUpload 拦截器
fileUpload 拦截器将文件和元数据从多重请求 ( multipart/form-data )转换为常规的请求数据 , 以便它们设置在对应的 Action 属性上,实现文件上传。也就是对文件上传提供支持。
5. validation 拦截器
validation 拦截器用于执行数据校验调用验证框架进行数据验证
6. workflow 拦截器
workflow 拦截器提供当数据校验错误时终止执行流程的功能
7. exception 拦截器
exception 拦截器用于捕获异常,并且能根据异常类型将捕获的异常映射到用户自定义的错误页面 . 此拦截器执行的时候应位于所定义的拦截器的第一个。
Struts2 还有很多有用的拦截器,以上这些只是其中的一部分,如果有需要可以查阅 struts-default.xml , 了解更多的Struts 2内置拦截器。
2.5 Struts 2 内置拦截器栈
Struts2 除了提供有用的拦截器外,还定义了一些拦截器栈 . 在开发Web应用程序时,可以直接引用这些拦截器栈,不用自己组合拦截器栈。
struts-default.xml
中定义了一个非常重要的拦截器栈——defaultStack拦截器栈 . 此拦截器栈组合了多个拦截器,这些拦截器的顺序经过了精心的设计,能够满足大多数Web应用程序发开发需求。
只要在定义包的时候中继承 struts-default
包,那么defaultStack
拦截器栈将是默认的拦截器引用。
defaultStack
拦截器栈定义如示例4所示:
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="datetime"/>
<interceptor-ref name="multiselect"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params"/>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="debugging"/>
</interceptor-stack>
<!-- The completeStack is here for backwards compatibility for
applications that still refer to the defaultStack by the
old name -->
<interceptor-stack name="completeStack">
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
<!-- Sample execute and wait stack.
Note: execAndWait should always be the *last* interceptor. -->
<interceptor-stack name="executeAndWaitStack">
<interceptor-ref name="execAndWait">
<param name="excludeMethods">input,back,cancel</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="execAndWait">
<param name="excludeMethods">input,back,cancel</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
Struts 2 为我们提供了如此丰富的拦截器 , 但是这并不意味着我们失去了创建自定义拦截器的能力 , 恰恰相反 , 自定义Struts 2拦截器是相当容易的一件事
2.6 自定义拦截器
在Struts2中支持自定义的拦截器,Sturts2中所有的拦截器直接或间接地实现接口com.opensymphony.xwork2.intercptor.Interceptor
。
接口提供了三个方法 , 如下所示:
-
void init():该拦截器被初始化后,在该拦截器执行拦截之前,系统回调该方法对于每个拦截器而言,此方法只执行一次。
-
void destroy():该方法与 init() 方法对应。在拦截器实例被销毁之前,系统将回调该方法。
-
String intercept( ActionInvocation invocation)throws Exception : 该方法是用户需要实现的拦截动作 ,该方法会返回一个字符串作为逻辑视图。
除此之外 , 继承
com.opensymphony.xwork2.intercptor.Interceptor.AbstractIntercept
类是更简单的一种实现拦截器的方式。 该类提供了init()和destroy()方法的空实现 , 我们只需要实现 intercept() 方法 , 就可以创建自己的拦截器了。
为租房网开发一个自定义拦截器来判断用户是否登录 .
当用户需要请求执行某个受保护的操作时 , 先检查用户是否登录 . 如果没有登录 , 则向用户显示登录页面 ; 如果用户已经登录 , 则继续操作 . 首先编写权限验证拦截器 , 代码如示例5所示:
示例5:
AuthorizationInterceptor.java
代码如下:
/**
* 实现登录权限的验证
*/
public class AuthorizationInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation actionInvocation) throws Exception {
Map<String, Object> session = actionInvocation.getInvocationContext().getSession();
System.out.println("权限验证拦截器");
Object login = session.get("login");
if (login == null) {
return Action.LOGIN;
}else{
return actionInvocation.invoke();
}
}
}
定义Default.java
实现关于用户登录失败时需要跳转的页面:
/**
* 提示失败信息的action
*/
public class Default extends ActionSupport {
@Override
public String execute() throws Exception {
System.out.println("执行action");
return "fail";
}
}
定义HouseAction.java
, 实现租户管理的需求:
/**
* 租户信息
*/
public class HouseAction extends ActionSupport {
@Override
public String execute() throws Exception {
//这里只为演示权限检查拦截器的作用,故忽略业务逻辑
return SUCCESS;
}
}
配置文件struts.xml
内容如示例6所示:
示例6:
<?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>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<!--配置拦截器-->
<interceptors>
<!--定义权限验证拦截器-->
<interceptor name="myAuthorization"
class="cn.strutsdemo.action.AuthorizationInterceptor" />
<!--定义拦截器栈-->
<interceptor-stack name="myStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="myAuthorization"/>
</interceptor-stack>
</interceptors>
<!--定义默认的拦截器引用-->
<default-interceptor-ref name="myStack"/>
<!--定义默认的action-->
<default-action-ref name="defaultAction"/>
<!--定义全局结果-->
<global-results>
<result name="login" type="redirect">/page/login.jsp</result>
</global-results>
<action name="defaultAction" class="cn.strutsdemo.action.Default">
<result name="fail">/page/fail.jsp</result>
</action>
<action name="houseAction" class="cn.strutsdemo.action.HouseAction">
<result>/page/manage.jsp</result>
</action>
</package>
</struts>
webapp
目录下 , 创建page
目录 , 并在此目录下分别创建login.jsp,fail.jsp,manage.jsp
,代码分别如下:
login.jsp
代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录页面</title>
</head>
<body>
<!--这里只为演示权限验证拦截器 , 故忽略其他代码-->
<h1>登录页面</h1>
</body>
</html>
fail.jsp
代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录失败页面</title>
</head>
<body>
<h1>登录失败</h1>
<!--这里只为演示权限验证拦截器 , 故忽略其他代码-->
<a href="login.jsp">返回</a>
</body>
</html>
manage.jsp
代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户管理</title>
</head>
<body>
<!--这里只为演示权限验证拦截器 , 故忽略其他代码-->
<h1>租房页面</h1>
</body>
</html>
部署项目 , 浏览器上输入的地址无论是http://localhost:8080/defaultAction
还是http://localhost:8080/houseAction
,因为session中未设置key("login"
)对应的value , 因此浏览器都会跳转到登录页面
, 但如果直接请求jsp页面 , 程序并没有跳转到登录页面 , 由此可以拦截器只针对Action的请求才会发生作用 .
以上的示例是拦截器对所有的Action都发生了作用 , 因为在
struts.xml
中全局引用默认的拦截器栈 , 代码证据<default-interceptor-ref name="myStack"/>
.
若想要拦截器只针对某个action起作用 , 比如登录成功之后跳转到用户管理页面 . 可以修改上面的案例 , 在defaultAction
中获取session , 并为key("login"
)设置对应的值 , 代码如下:
package cn.strutsdemo.action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
/**
* 提示失败信息的action
*/
public class Default extends ActionSupport {
@Override
public String execute() throws Exception {
ActionContext.getContext().getSession().put("login","username");
return "fail";
}
}
修改struts.xml
文件 , 在用户管理请求引用拦截器栈 , 并将默认的拦截器栈注释掉 , 代码如下:
<?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>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<!--配置拦截器-->
<interceptors>
<!--定义权限验证拦截器-->
<interceptor name="myAuthorization"
class="cn.strutsdemo.action.AuthorizationInterceptor"/>
<!--定义拦截器栈-->
<interceptor-stack name="myStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="myAuthorization"/>
</interceptor-stack>
</interceptors>
<!--定义默认的action-->
<default-action-ref name="defaultAction"/>
<!--定义全局结果-->
<global-results>
<result name="login" type="redirect">/page/login.jsp</result>
</global-results>
<action name="defaultAction" class="cn.strutsdemo.action.Default">
<result name="fail">/page/fail.jsp</result>
</action>
<action name="houseAction" class="cn.strutsdemo.action.HouseAction">
<result>/page/manage.jsp</result>
<interceptor-ref name="myStack"/>
</action>
</package>
</struts>
重新部署项目 , 浏览器中先输入地址:http://localhost:8080/defaultAction
, 然后再输入地址:http://localhost:8080/houseAction
, 因为session中的login
有了对应的value , 所以会放行 . 浏览器会跳转到对应的用户管理页面.
2.7 总结拦截器与过滤器的区别:
- 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
- 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
- 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
- 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
- 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用
3 . Struts 2 实现文件上传
文件上传是一个经常用到的功能 , 在Struts2中已经封装好了上传的组件,只需要在程序中简单设置即可实现文件上传。
3.1 准备工作
在Struts 2 框架中提供了对commons-fileupload
组件的支持 , 并且默认使用该组件实现文件上传 , 因此 , 为了实现文件上传功能 , 我们需要在项目中包含两个jar文件:commons-fileupload-x.x.x.jar
, commons-io-x.x.x.jar
.
说明:jar文件的版本取决于当前工程使用的Struts 2版本
3.2 实现文件上传
1. 上传页面的准备
制作一个简单页面 , 用于实现文件上传 , 制作页面的代码如示例7所示:
示例7:
fileinput.jsp
代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>演示单个文件上传</title>
</head>
<body>
<form action="upload.action" method="POST"
enctype="multipart/form-data">
标题:<input type="text" name="title"><br>
文件一:<input type="file" name="upload"><br>
<input type="submit" value="上传文件">
</form>
</body>
</html>
备注:表单必须设置enctype
属性
2. 开发实现文件上传的Action
实现文件上传的代码如示例8所示:
/**
* 实现文件上传
*/
public class UploadAction extends ActionSupport {
private static final Logger LOG = LogManager.getLogger(UploadAction.class);
//封装文件标题属性
private String title;
//封装上传到服务器的文件对象
private File upload;
//封装上传文件的类型
private String uploadContentType;
//封装上传文件的名称
private String uploadFileName;
@Override
public String execute() throws Exception {
//文件的信息:
LOG.info("file:"+this.getUpload());
LOG.info("fileName:"+this.getUploadFileName());
LOG.info("contentType:"+this.getUploadContentType());
//构建要保存文件夹的物理路径(绝对路径)
//ServletActionContext.getServletContext()可以定位到webapp目录下
String realPath =
ServletActionContext.getServletContext().getRealPath("upload");
//构建文件上传的目录
File file = new File(realPath);
//测试此抽象路径名表示的文件或目录是否存在。
//若不存在,创建此抽象路径名指定的目录(包括所有必需但不存在的父目录)
if(!file.exists()){
file.mkdirs();
}
//保存文件
try {
//可以使用时间戳+随机数构建新的文件名
//保证文件名不重复 文件可以重复提交
//uploadFileName =
// System.currentTimeMillis()+"_"+ RandomUtils.nextInt()+
// ".jpg";
File destFile = new File(file,uploadFileName);
FileUtils.copyFile(upload,destFile);
//显示文件的绝对路径
LOG.info(destFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
return SUCCESS;
}
//省略getter setter
}
需要特别强调的是 ,在Action中使用了三个属性来封装文件信息 , 分别如下:
- File类型的xxx属性 : 与表单中的File控件的name属性一致 , 用于封装File控件对应的文件内容
- String 类型的xxxFileName属性 : 该属性名称由前面的File类型属性和"FileName"组合而成 , 是固定的语法 , 其作用是封装File控件对应文件的文件名,可参考源码
org.apache.struts2.interceptor.FileUploadInterceptor
- String 类型的xxxContentType属性 : 该属性名称同样由前面的File类型属性和"ContextType"组合而成 , 是固定语法 , 其作用是封装File控件对应文件的文件类型 , 可参考源码
org.apache.struts2.interceptor.FileUploadInterceptor
有了这三个属性 , 在执行文件上传时就可以直接通过getter方法来获取上传文件的文件名,类型以及文件内容.
3. 配置文件上传的Action
Action编写完毕后 , 下一步就需要进行配置 , 配置Action的方式很简单 , 代码如示例9所示:
示例9:
<?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>
<!-- 要注意Struts2默认文件上传最大为2M即便你设置了
<param name="maximumSize">5242880</param>
当上传的文件大于2M时候也会出错的这时要设置另外一个常量-->
<constant name="struts.multipart.maxSize" value="500000000"/>
<constant name="struts.ui.theme" value="simple"/>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<action name="upload"
class="cn.strutsdemo.action.UploadAction">
<!--defaultStack:
指定(限制)上传文件的类型,定义局部拦截器,修改默认拦截器的属性
"fileUpload.maximumSize" :限制上传最大的文件大小。
"fileUpload.allowedTypes":允许上传文件的类型。根据实际需求,可以不配置此属性
"fileUpload.allowedExtensions":允许上传文件的可扩展文件类型,即指定文件后缀。
备注:关于文件类型的限制,配置allowedTypes或者配置allowedExtensions,二者配置其一即可-->
<interceptor-ref name="defaultStack">
<param name="fileUpload.maximumSize">5000000</param>
<param name="fileUpload.allowedTypes">text/plain,
application/vnd.ms-powerpoint,image/png</param>
<!-- 允许后缀名为png,bmp,jpg,doc,xls的文件上传 -->
<param name="fileUpload.allowedExtensions">png,bmp,jpg,doc,xls</param>
</interceptor-ref>
<result name="success">/success.jsp</result>
<result name="input">/error.jsp</result>
</action>
</package>
</struts>
附加:contentType类型的设置
文件类型 | 类型设置 |
---|---|
Word | application/msword |
Excel | application/vnd.ms-excel |
PPT | application/vnd.ms-powerpoint |
图片 | image/gif , image/bmp,image/jpeg |
文本文件 | text/plain |
html网页 | text/html |
任意二进制数据 | application/octet-stream |
编写上传成功success.jsp
页面 , 示例代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String path = request.getContextPath();
//${pageContext.request.contextPath }
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<html>
<head>
<base href="<%=basePath%>">
<title>上传成功</title>
</head>
<body>
您所上传的文件名是:<s:property value="uploadFileName"/><br/>
文件类型:<s:property value="uploadContentType"/><br/>
<!--basePath也可替换为${pageContext.request.contextPath }获取该项目的绝对路径 -->
图片: <img src="<%=basePath%>/upload/<s:property value='uploadFileName'/>" />
</body>
</html>
编写上传失败error.jsp
页面 , 代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>上传失败</title>
</head>
<body>
跳转错误页面。。<br>
<%--<s:actionerror/>--%>
<!--显示错误信息 -->
<s:fielderror/>
</body>
</html>
部署项目 , 在结果页面中输入上传文件文件的标题以及文件类型 , 效果如图所示:
点击上传文件 , 效果如图所示:
3.3 实现多文件上传
实现多文件上传的操作非常简单 , 在表单中添加多个相同的name属性的File控件 , 这样当表单提交时 , 将会提交一个数组 , 因此只需要在上传Action中将原本处理单个单个文件的操作改成对数组的操作即可 .
修改实现文件上传的Action , 以满足多文件的上传 , 如示例11所示:
示例11:
UploadAction.java
代码如下:
/**
* 实现多文件上传
*/
public class UploadAction extends ActionSupport {
private static final Logger LOG = LogManager.getLogger(UploadAction.class);
//封装文件标题属性
private String title;
//封装上传到服务器的文件对象
private File[] upload;
//封装上传文件的类型
private String[] uploadContentType;
//封装上传文件的名称
private String[] uploadFileName;
@Override
public String execute() throws Exception {
//文件的信息:
for (File f : this.getUpload()) {
LOG.info("file:"+f);
}
for (String s : this.getUploadFileName()) {
LOG.info("fileName:"+s);
}
for (String s : this.getUploadContentType()) {
LOG.info("contentType:"+s);
}
//构建要保存文件夹的物理路径(绝对路径)
//getRealPath:方法是获取当前项目的绝对磁盘路径
//
String realPath =
ServletActionContext.getServletContext().getRealPath("upload");
//构建文件上传的目录
File file = new File(realPath);
//测试此抽象路径名表示的文件或目录是否存在。
// 若不存在,创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。
if(!file.exists()){
file.mkdirs();
}
//保存文件
try {
for (int i = 0; i < this.getUpload().length; i++) {
//可以使用时间戳+随机数构建新的文件名 文件可以重复提交
//uploadFileName[i] = System.currentTimeMillis()+"_"+ RandomUtils.nextInt()+
//".jpg";
File destFile = new File(file,uploadFileName[i]);
FileUtils.copyFile(this.getUpload()[i],destFile);
LOG.info(destFile.getAbsolutePath());
}
} catch (IOException e) {
e.printStackTrace();
}
return SUCCESS;
}
//省略getter setter
}
struts.xml
内容不用修改 , 修改fileinput.jsp
文件 , 增加一个File控件 , 并指定name跟第一个File控件的name值一致 , 代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>演示多个文件上传</title>
</head>
<body>
<form action="upload.action" method="POST"
enctype="multipart/form-data">
标题:<input type="text" name="title"><br>
文件一:<input type="file" name="upload"><br>
文件二:<input type="file" name="upload"><br>
<input type="submit" value="上传文件">
</form>
</body>
</html>
修改上传成功之后的页面(success.jsp
) , 代码如下:
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<html>
<head>
<base href="<%=basePath%>">
<title>上传成功</title>
</head>
<body>
您所上传的文件是:<%--<s:property value="uploadFileName"/><br/>--%>
<s:if test="uploadFileName!=null">
<s:iterator value="uploadFileName" var="name">
<s:property value="name"/><br/>
</s:iterator>
</s:if>
文件类型:<%--<s:property value="uploadContentType"/><br/>--%>
<s:if test="uploadContentType!=null">
<s:iterator value="uploadContentType" var="type">
<s:property value="type"/><br>
</s:iterator>
</s:if>
图片:
<!--basePath也可替换为${pageContext.request.contextPath }获取该项目的绝对路径 -->
<s:if test="uploadFileName!=null">
<s:iterator value="uploadFileName" var="name">
<img src="<%=basePath%>/upload/<s:property value='name'/>" width="200" height="200"/><br/>
</s:iterator>
</s:if>
</body>
</html>
重新部署项目 , 输入文件上传的标题以及选择文件类型 , 效果如下:
点击上传文件 , 效果如图所示:
提示 : 实现多文件上传 , 还可以采用多个File控件 , 不同name属性的方式 , 不过这样每增加一个File控件 , 都必须相应的增加属性设置 , 会造成Action中属性过多的情况 , 因此不建议使用这种方式 .
4 . Struts 2 实现文件下载
Struts2框架提供了 stream 结果类型,该类型的作用就是专门实现文件下载功能
4.1 stream 结果类型
stream结果类型用于实现文件下载功能 , 在实现功能时需要指定一个输入流 , 即inputStream
参数 , 通过这个流就可以读取需要下载的文件内容 .
当然 , 实现文件下载也并非如此简单 , 我们还需要对相关的参数进行配置 , 如MIME类型
,HTTP
请求头信息 , 缓冲区大小等 .
stream
结果类型的配置参数如表:
名称 | 作用 |
---|---|
contentType | 设置发送到浏览器的MIME类型 |
contentLength | 设置文件的大小 |
contentDisposition | 设置响应的HTTP头信息中的Content-Disposition参数的值 |
inputName | 指定Action中提供的inputStream类型的属性名称 |
bufferSize | 设置读取和下载文件时的缓冲区大小 |
4.2 实现文件下载
Struts 2框架支持文件下载功能 , 下面通过分步的方式实现文件的下载功能.
1. 定义InputStream
在Struts 2中实现文件下载时需要用到InputStream
, 所以在文件下载Action中提供一个获得InputStream的方法 , 通过这个输入流就可以获取希望下载的文件内容 .代码如示例12所示:
示例12:
/**
* 实现文件下载
*/
public class FileDownAction extends ActionSupport {
//读取下载文件的目录
private String inputPath;
//下载文件的文件名
private String fileName;
//读取下载文件的输入流
private InputStream inputStream;
//下载文件的类型
private String contentType;
public InputStream getInputStream() throws FileNotFoundException {
//获取下载的绝对路径
String path =
ServletActionContext.getServletContext().getRealPath(inputPath);
//注意是目录+“\”+文件名
return new BufferedInputStream(new FileInputStream(path+File.separator+fileName));
}
@Override
public String execute() throws Exception {
return SUCCESS;
}
public String getInputPath() {
return inputPath;
}
public void setInputPath(String inputPath) {
this.inputPath = inputPath;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
}
在示例12中 , 通过ServletContext上下文得到下载文件的实际路径 , 并构建一个InputStream
输入流实现文件的读取 .
2. 配置stream结果类型
在配置文件中 , 同样要对下载Action进行配置 , 并且要对stream结果类型的参数进行设置 , 代码如示例13所示 :
示例13:
<?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>
<!-- 要注意Struts2默认文件上传最大为2M即便你设置了
<param name="maximumSize">5242880</param>
当上传的文件大于2M时候也会出错的这时要设置另外一个常量-->
<constant name="struts.multipart.maxSize" value="500000000"/>
<constant name="struts.ui.theme" value="simple"/>
<constant name="struts.devMode" value="true"/>
<package name="default" namespace="/" extends="struts-default">
<action name="download"
class="cn.strutsdemo.action.FileDownAction">
<!--配置指定下载文件的目录-->
<param name="inputPath">upload</param>
<result name="success" type="stream">
<!--contentType:指定被下载文件的文件类型
inputName:指定被下载文件的入口输入流
contentDisposition:指定下载的文件名
bufferSize:指定下载文件时的缓冲大小-->
<param name="contentType">
application/octet-stream
</param>
<!--inputName默认为inputStream 所以可以不用配置-->
<!--<param name="inputName">inputStream</param>-->
<!--contentDisposition:配置指定下载的文件名-->
<param name="contentDisposition">
attachment;filename="${fileName}"
</param>
<!-- bufferSize:指定下载文件时的缓冲大小-->
<param name="bufferSize">4096</param>
</result>
</action>
</package>
</struts>
在配置文件中 , contentType
参数决定了下载文件的类型 . 不同的文件类型对应的参数值也不是相同的 , 如下表所示:
文件类型 | 类型设置 |
---|---|
Word | application/msword |
Excel | application/vnd.ms-excel |
PPT | application/vnd.ms-powerpoint |
图片 | image/gif , image/bmp,image/jpeg |
文本文件 | text/plain |
html网页 | text/html |
任意二进制数据 | application/octet-stream |
提示:通常情况下 , contentType
参数直接设置为application/octet-stream
即可
-
contentDispoistion
参数由两部分组成 , 前面的部分表示处理文件的形式 , 如attachment
表示在下载时弹出对话框 , 提示用户保存或者直接打开该文件 ; 后一部分filename
表示下载文件的文件名称 . 两部分以";"
进行分隔.-
文件下载的处理方式,包括内联(inline)和附件(attachment)两种方式,而附件方式会弹出文件保存对话框,内联的方式浏览器会尝试直接显示文件(仅限IE)。取值为:
-
attachment;filename=“readme.txt”,表示文件下载的时候保存的名字应为readme.txt。
-
如果直接写filename=“readme.txt”,那么默认情况是代表inline,浏览器会尝试自动打开它,等价于这样的写法:inline;filename="readme.txt"
Response.addHeader("Content-Disposition","attachment;filename=FileName.txt");
-
备注:这样浏览器会提示保存还是打开,即使选择打开,也会使用相关联的程序比如记事本打开,而不是IE直接打开了
关于param参数的设置可参考源码:org.apache.struts2.result.StrutsResultSupport
3. 开发简单的下载页面
最后我们开发一个简单的下载页面 , 在页面中设置一个超链接 , 并通过超链接请求下载Action , 代码如示例14所示:
示例14:
filedown.jsp
代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>演示文件下载</title>
</head>
<body>
<a href="download.action?fileName=03.png">点击此处下载文件</a>
</body>
</html>
部署项目 , 除了IE
浏览器之外 , 都是直接下载文件 , 如图:
若是IE
浏览器 , 则会出现下载框提示 , 效果如图所示: