SSH Chapter 11 Struts2 拦截器

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的文档中提供了如图所示的体系结构图:
SSH Chapter 11 Struts2 拦截器_第1张图片

  • 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 体系结构

  1. 当Web容器收到一个请求时,它将请求传递给一个标准的过滤器链,其中包括ActionContentCleanUp过滤器和其他过滤器(如集成SiteMess的插件),这是非常有用的技术。接下来,需要调用FilterDispatcher,它将调用ActionMapper,来确定请求调用哪个Action,ActionMapper返回一个收集了Action详细信息的ActionMapping对象
  2. 接下来,FilterDispatcher将控制权委派给ActionProxy,ActionProxy调用配置管理器(Configuration Manager)从配置文件中读取配置信息,然后创建ActionInvocation对象,实际上,ActionInvocation的处理过程就是Struts2处理请求的过程
  3. 一旦Action执行返回结果字符串,ActionInvocation负责查找结果字符串对应的Result,然后执行这个Result。通常情况下Result会调用一些模板(JSP等)来呈现页面
  4. 之后,拦截器会被再次执行(顺序与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方法即可。

运行流程解析如下:

  1. 客户端向Servlet容器(如Tomcat)提交一个请求
  2. 请求经过一系列过滤器(如ActionContextCleanUp过滤器等); 备注:从struts2.1.3开始ActionContextCleanUp 和 FilterDispatcher过滤器,已经不建议使用了。将使用StrutsPrepareFilter和StrutsExecuteFilter拦截器替代
  3. 核心控制器被调用,询问ActionMapper来决定请求是否需要调用某个Action
  4. 如果ActionMapper决定需要调用某个Action,核心控制器把控制权委派给ActionProxy (备注:JSP请求无需调用Action)
  5. ActionProxy通过Configuration Manager询问框架的配置文件(struts.xml),找到需调用的Action类
  6. ActionProxy创建一个ActionInvocation的实例
  7. ActionInvocation负责调用Action,在此之前会依次调用所有配置的拦截器
  8. Action执行完毕,ActionInvocation负责根据结果字符串在struts.xml的配置中找到对应的返回结果
  9. 根据结果(Result)找到页面后,在页面上(有很多Struts2提供的模板),可以通过Struts2自带的标签库来访问需要的数据,并生成最终页面注意:这时还没有给客户端应答,只是生成了页面
  10. 最后,ActionInvocation对象倒序执行拦截器
  11. 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 的实现原理差不多,以链式执行,对真正要执行的方法进行拦截 .
SSH Chapter 11 Struts2 拦截器_第2张图片

首先执行Action配置的拦截器,在Action和Result执行之后,拦截器再一次执行,与之前调用相反的顺序,在此链式的执行过程中,任何一个拦截器都可以直接返回,从而终止余下的拦截器、Action及Result的执行。

当ActionInvocation的invoke()方法调用时,开始执行Action配置的第一个拦截器 , 拦截器做出相应的处理后会再次调用ActionInvocation的invoke()方法 , ActionInvocation对象负责跟踪执行过程到状态,并把控制权交个合适的拦截器。

ActionInvocation通过调用拦截器的 intercept()方法将控制转交给拦截器。因此 , 拦截器的执行过程可以看作是一个递归的过程,后续拦截器继续执行,直到最后一个拦截器,invoke()方法会执行Action.

拦截器有一个三阶段、有条件的执行周期 , 如下所示:

  1. 首先做一些Action执行前的预处理。拦截器可以准备、过滤、改变或者操作任何可以访问的数据,包括Action
  2. 调用ActionInvocation的invoke()方法将控制权转交给后续的拦截器或者返回结果字符终止执行,如果拦截器决定请求的处理不应该继续 , 可以不调用invoke()方法 , 而是直接返回一个控制字符串 . 通过这种方式,可以停止后续的执行,并且决定将哪个结果呈现给客户端。
  3. 做一些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 拦截器的配置

配置拦截需要经过两个步骤:

  1. 通过 元素来定义拦截器
  1. 通过元素来使用拦截器

定义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所示:





<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.jspresult>
			
            <interceptor-ref name="myTimer"/>
            <interceptor-ref name="defaultStack"/>
        action>
    package>
struts>

说明:

struts.xml文件中 , 首先在元素中使用元素来定义拦截器 , 元素的name属性和class属性是必须填写的 , 前者指定拦截器的名字 , 后者指定拦截器的全限定类名 . 然后在元素中使用子元素指定引用的拦截器 .

如果除了希望调用自己编写的拦截器外 , 还希望调用Struts2框架定义的默认拦截器 , 就需要将默认拦截器一并添加到元素中 , 有关 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, 使用拦截器栈 ,内容如下:





<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.jspresult>
            
            <interceptor-ref name="myTimerStack"/>
            
        action>
    package>
struts>

如果想要把多个拦截器组成一个拦截器栈,就需要在 interceptors 元素中使用interceptor-stack 元素定义拦截器栈 , 其中name属性指定拦截器栈的名称 , 依然使用intercept-ref元素指定引用的拦截器.

解释:引用拦截器时,Struts2并不区分拦截器和拦截器栈,因此在定义拦截器时,可以引用其他的拦截器栈。

如果配置文件大多数Action都引用相同的拦截器,可以使用默认的拦截器引用。 元素用来定义默认的拦截器引用 , 其name属性指定引用的拦截器或拦截器栈的名称。

如下所示:

可以继续简化struts.xml , 内容如下:





<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.jspresult>
            
            
            
        action>
    package>
struts>

2.4 Struts 2 内置拦截器

在Struts2框架中,内置了很多拦截器供开发人员使用。

1. params 拦截器

提供了框架必不可少的功能,将请求中的数据设置到Action的属性上。负责将请求参数设置成action属性。

2. staticParams 拦截器

staticParams拦截器是将在配置文件中通过 元素的子元素设置的参数设置到对于到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,browseparam>
    interceptor-ref>
    <interceptor-ref name="workflow">
    	<param name="excludeMethods">input,back,cancel,browseparam>
    interceptor-ref>
    <interceptor-ref name="debugging"/>
interceptor-stack>


<interceptor-stack name="completeStack">
  	<interceptor-ref name="defaultStack"/>
interceptor-stack>


<interceptor-stack name="executeAndWaitStack">
    <interceptor-ref name="execAndWait">
    	<param name="excludeMethods">input,back,cancelparam>
    interceptor-ref>
    <interceptor-ref name="defaultStack"/>
    <interceptor-ref name="execAndWait">
    	<param name="excludeMethods">input,back,cancelparam>
    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:





<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"/>
        
        <default-action-ref name="defaultAction"/>
        
        <global-results>
            <result name="login" type="redirect">/page/login.jspresult>
        global-results>
        <action name="defaultAction" class="cn.strutsdemo.action.Default">
            <result name="fail">/page/fail.jspresult>
        action>
        <action name="houseAction" class="cn.strutsdemo.action.HouseAction">
            <result>/page/manage.jspresult>
        action>
    package>
struts>

webapp目录下 , 创建page目录 , 并在此目录下分别创建login.jsp,fail.jsp,manage.jsp ,代码分别如下:

login.jsp代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    登录页面


    
    

登录页面

fail.jsp代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    登录失败页面


    

登录失败

返回

manage.jsp代码如下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    用户管理


     
    

租房页面

部署项目 , 浏览器上输入的地址无论是http://localhost:8080/defaultAction 还是http://localhost:8080/houseAction ,因为session中未设置key("login")对应的value , 因此浏览器都会跳转到登录页面 , 但如果直接请求jsp页面 , 程序并没有跳转到登录页面 , 由此可以拦截器只针对Action的请求才会发生作用 .

以上的示例是拦截器对所有的Action都发生了作用 , 因为在struts.xml中全局引用默认的拦截器栈 , 代码证据 .

若想要拦截器只针对某个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 文件 , 在用户管理请求引用拦截器栈 , 并将默认的拦截器栈注释掉 , 代码如下:





<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-action-ref name="defaultAction"/>
        
        <global-results>
            <result name="login" type="redirect">/page/login.jspresult>
        global-results>
        <action name="defaultAction" class="cn.strutsdemo.action.Default">
            <result name="fail">/page/fail.jspresult>
        action>
        <action name="houseAction" class="cn.strutsdemo.action.HouseAction">
            <result>/page/manage.jspresult>
            <interceptor-ref name="myStack"/>
        action>
    package>
struts>

重新部署项目 , 浏览器中先输入地址:http://localhost:8080/defaultAction , 然后再输入地址:http://localhost:8080/houseAction , 因为session中的login有了对应的value , 所以会放行 . 浏览器会跳转到对应的用户管理页面.

2.7 总结拦截器与过滤器的区别:

  1. 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
  2. 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
  3. 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
  4. 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
  5. 在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" %>


    演示单个文件上传


    
标题:
文件一:

备注:表单必须设置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:





<struts>
    
    <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">
            
            <interceptor-ref name="defaultStack">
                <param name="fileUpload.maximumSize">5000000param>
                <param name="fileUpload.allowedTypes">text/plain,
                    application/vnd.ms-powerpoint,image/pngparam>
				  
                <param name="fileUpload.allowedExtensions">png,bmp,jpg,doc,xlsparam>
            interceptor-ref>
            <result name="success">/success.jspresult>
            <result name="input">/error.jspresult>
        action>
    package>
struts>

附加:contentType类型的设置

文件类型 类型设置
Word application/msword
Excel application/vnd.ms-excel
PPT application/vnd.ms-powerpoint
图片 image/gif image/bmpimage/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+"/";
%>


    
    上传成功


    
您所上传的文件名是:
文件类型:
图片:

编写上传失败error.jsp页面 , 代码如下:

<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    上传失败


跳转错误页面。。
<%----%>

部署项目 , 在结果页面中输入上传文件文件的标题以及文件类型 , 效果如图所示:
SSH Chapter 11 Struts2 拦截器_第3张图片
点击上传文件 , 效果如图所示:
SSH Chapter 11 Struts2 拦截器_第4张图片

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" %>


    演示多个文件上传


    
标题:
文件一:
文件二:

修改上传成功之后的页面(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+"/";
%>


    
    上传成功


您所上传的文件是:<%--
--%>
文件类型:<%--
--%>
图片:

重新部署项目 , 输入文件上传的标题以及选择文件类型 , 效果如下:
SSH Chapter 11 Struts2 拦截器_第5张图片

点击上传文件 , 效果如图所示:
SSH Chapter 11 Struts2 拦截器_第6张图片

提示 : 实现多文件上传 , 还可以采用多个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:





<struts>
    
    <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">uploadparam>
            <result name="success" type="stream">
                
                <param name="contentType">
					application/octet-stream
				param>
				
              
                
				<param name="contentDisposition">
                    attachment;filename="${fileName}"
                param>
				
                <param name="bufferSize">4096param>
            result>
        action>
    package>
struts>

在配置文件中 , contentType参数决定了下载文件的类型 . 不同的文件类型对应的参数值也不是相同的 , 如下表所示:

文件类型 类型设置
Word application/msword
Excel application/vnd.ms-excel
PPT application/vnd.ms-powerpoint
图片 image/gifimage/bmpimage/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" %>


    演示文件下载


    点击此处下载文件



部署项目 , 除了IE浏览器之外 , 都是直接下载文件 , 如图:
SSH Chapter 11 Struts2 拦截器_第7张图片

若是IE浏览器 , 则会出现下载框提示 , 效果如图所示:
SSH Chapter 11 Struts2 拦截器_第8张图片

你可能感兴趣的:(ssh)