旅行的意义

 

Struts2的拦截器

struts2的拦截器(Interceptor)是struts中比较复杂也比较核心的内容,功能挺强大。不过对于这种很难很强大的东东,一般还是比较抵触的:毕竟学起来比较艰深,学完了有没有用又另当别论了——那为什么还要学呢?
如果不能解决实际问题,它吹得再牛,也不见得有多好。所以,拦截器到底做什么的呢?

拦截器的一个最广泛的应用是全局访问权限控制。


在做网站的时候,一般来说都有全局的权限控制这一块。如果在每个页面的前头加一段代码的话,页面少还问题不大,页面多了维护起来就比较恐怖了。Struts中提供了一种全局性的解决方案:拦截器。

拦截器的底层实现机制还是AOP,其原理是……AOP的实现原理就不说了,拦截器的原理是在特定函数的执行前后,插入自定义方法,具体到对Action拦截器中,就是每个(或者每个指定的)Action的Execute()或者其他指定方法执行前后,插入自定义的方法。

也就是说,你可以在(除了登录Action本身之外)所有Action逻辑发生之前,插入一段用户验证逻辑,从而将绕过登录界面的用户请求挡在核心业务逻辑之外;进一步的,可以将各种业务按照权限分级分类,使用不同的拦截器逻辑进行拦截,从而达到多级权限控制的效果。


上面说的只是拦截器的一部分功能,其实struts自己也预定义了许多拦截器,比如说。Action映射、参数解析、日志、国际化 、验证……struts中许多非常核心的功能其实都是通过拦截器机制来实现的。如果有兴趣了解,struts2-core-xxx.jar中struts-default.xml的<interceptors>节点可以作为起点,其中定义了struts内建的所有拦截器。


下面简单记录实现步骤
1、定义拦截器。

Struts2规定用户自定义拦截器必须实现com.opensymphony.xwork2.interceptor.Interceptor接口
。该接口声明了3个方法,

void init();
void destroy();
String intercept(ActionInvocation invocation) 
throws Exception;

其中,init和destroy方法会在程序开始和结束时各执行一遍,不管使用了该拦截器与否,只要在struts.xml中声明了该拦截器就会被执行。
intercept方法就是拦截的主体了,每次拦截器生效时都会执行其中的逻辑。

不过,struts中又提供了几个抽象类来简化这一步骤。
public abstract class AbstractInterceptor implements Interceptor;
public abstract class MethodFilterInterceptor extends AbstractInterceptor;
都是模板方法实现的。
其中AbstractInterceptor提供了init()和destroy()的空实现,使用时只需要覆盖intercept()方法;
MethodFilterInterceptor则提供了includeMethods和excludeMethods两个属性,用来过滤执行该过滤器的action的方法。可以通过param来加入或者排除需要过滤的方法。

一般来说,拦截器的写法都差不多。看下面的示例:

 

public class AuthInterceptor extends MethodFilterInterceptor{
    
protected String doIntercept(ActionInvocation invocation) throws Exception {        
        Map session 
= invocation.getInvocationContext().getSession();
        
//如果session中存在username字段,且传入Action实现了Authenticatable接口,且验证成功
        
//当然这里只是举例子,具体要根据业务逻辑来写
        if(session.containsKey("username")&& (invocation instanceof Authenticatable)
               
&& ((Authenticatable)invocation).validate((String)session.get("username"),(String)session.get("password"))){
            
//将数据流转给真正的Action
            return invocation.invoke();
        }
else{
            
//返回一个全局登录失败的result
            return Action.LOGIN;
        }

    }

}

其中,if()逻辑内执行了从session中取用户信息及验证的逻辑,如果成功,则调用invocation.invoke()将逻辑交给Action,否则退出并返回一个全局的Login结果。


2.声明拦截器
拦截器需要在struts.xml中声明。在说明步骤之前,先介绍一下struts中拦截器的框架。

在struts中拦截器实际上分为拦截器和拦截器栈,拦截器栈可以包含一到多个拦截器或者拦截器栈。从上层看来,拦截器和拦截器栈实际上没有什么区别(就像操作系统中的文件夹和文件)。struts在入口处递归的调用了<default-interceptor-ref>中定义的拦截器(栈)中的所有拦截器。

其实之前如果看过struts-default.xml的话,可以看到struts中内建了许多的拦截器,事实上,即便我们在struts.xml中什么都不声明,程序也会在后台执行缺省拦截器栈中定义的许多拦截器逻辑,比如说将页面上的field映射到对应Action的同名属性中、自动执行类型转换、自动关联验证xml等等。这些拦截器会在每个没有显式声明拦截器的Action执行前后被执行。

需要注意的是,正如上面一句所提到的,如果在Action中显式声明了一个拦截器,那么系统默认的拦截器将不会被调用。因此,如果直接将自定义的拦截器放入Action中的话,内建的那些拦截器将会被忽略,这会导致错误。所以我们需要在struts.xml<package>元素下覆盖缺省拦截器。像下面这样:

 

<interceptors>
            
<interceptor-stack name="default-with-my-inteceptor">
                
<interceptor-ref name="defaultStack"/>
                
<interceptor-ref name="XXX-interceptor"/>
            
</interceptor-stack>
            
<interceptor name="XXX-interceptor" class="com.dev.interceptors.XXXInterceptor" />
</interceptors>

<default-interceptor-ref name="default-with-my-inteceptor"/>

 

这样就将自定义拦截器加上了struts缺省拦截器形成新的缺省拦截器。

因为全局定义了拦截器,虽然拦截器在通过拦截的情况下会返回特定Action的result,但有时候比如权限验证失败等情况下,自定义拦截器会返回自定义的结果,不属于任何特定Action,所以我们也需要定义一个全局result用以响应这个拦截器的返回值。
紧挨在<default-interceptor-ref>元素下面添加

<global-results>
    
<result name="" type="redirectAction">global_error.jsp</result>
</global-results>


注意到其result type为redirectAction重定向Action,这样struts将以重定向方式处理该Action,并跳转到global_error.jsp而不会显示出最后一次执行的Action名字。顺便,Action的result缺省是以dispatcher也就是请求转发的方式处理的。

同时,按照业务逻辑,有些特定Action是不能执行自定义拦截器的。比如说,如果我们定义了一个全局的拦截器,它从session中取出用户名和密码进行验证,验证通过则继续,不通过则返回到login.jsp页面,那么很显然login.jsp页面提交的那个Action本身是不能使用该拦截器的,否则就没有地方可以将用户信息放入session了。
这种情况下,就要借助之前提到的“特定Action中定义的拦截器会覆盖全局设置”这个特性了。在需要屏蔽该拦截器的个别Action中显式的声明defaultStack拦截器(也就是struts内建的拦截器栈),这样,自定义拦截器在这个Action中就不会生效了。


小结一下:
写了这么多,其实大部分都在讲原理,如果理解了原理,这几部做起来是否常容易的。所以重要的是掌握原理。
回忆一下,真正要做的只有三个地方:
(1)写一个拦截器类(实现接口也好,继承抽象类也好)
(2)修改struts.xml声明新的缺省拦截器栈和全局result
(3)对于特定Action,显式声明defaultStack拦截器以屏蔽自定义拦截器

另外谈一点自己的看法:
拦截器可以说是struts当之无愧的核心,无论是struts的实现原理还是我们基于struts搭建的应用扩展,拦截器都发挥了重要的作用。当然也有可能是我自己的理解不够深入,但是有一点没想明白,就是拦截器只能基于Action发挥效用,如果一些功能和Action无关,那么拦截器没法发生效用。不知道struts底层实现细节中是否只开放了基于Action的拦截器,其实理论上来说,在web.xml中设置了struts作为filter,并拦截所有请求,拦截器在从页面发送至服务器被struts filter到的那一刻起(准确地说是“前一刻起”),拦截器就能发挥作用了。比如说现在这种过滤器只能在页面提交到Action时起作用,但无法防止用户直接访问JSP。

posted on 2009-02-06 14:42 bacon 阅读(4066) 评论(1)  编辑  收藏 所属分类: J2EE

评论

# re: Struts2的拦截器 2010-07-22 17:56 masuz

在struts.xml中修改一下action扩展名的配置,应该就能使拦截器处理 .jsp的请求了:
<constant name="struts.action.extension" value="action,jsp" />

就是把 .jsp 请求也当作action拦截下来进行处理
  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

常用链接

留言簿(1)

随笔分类

随笔档案

文章分类

搜索

最新评论

阅读排行榜

评论排行榜