Struts2 源码分析——拦截器的机制

本章简言

上一章讲到关于action代理类的工作。即是如何去找对应的action配置信息,并执行action类的实例。而这一章笔者将讲到在执行action需要用到的拦截器。为什么要讲拦截器呢?可以这样子讲吧。拦截器的应用是sturts2核心的亮点之一。如果不明白拦截器是什么的话,那么你相当于没有学习过struts2。笔者本来想直接讲这一章的知识点。可是又怕读者可能对拦截器没有一个概念化的理解。为什么这么讲呢?struts2在设计拦截器这一个部分的内容。在笔者看来事实是以AOP为核心思想来设计的。所以就是必须先理解一下AOP思想到底是什么东东。只有这样子才能更好的去理解struts2的拦截器。

AOP思想

 AOP思想的全名为Aspect Oriented Programming。即是面向切面编程。相信读者者听过OOP(Object-Oriented Programing,面向对象编程)。笔者也认AOP是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。为什么笔者这边用“也”这个字呢?笔者不是一个喜欢吹牛的人。会就是会,不会就是不会。网络上有很多关于AOP思想的资料,笔者就是通过这些资料学习的,也认同AOP是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。所以笔者这里也只是简章的讲解一下AOP思想。希望读者见谅。

什么叫做面向切面编程呢?如果用专业的角度来讲切面叫作Aspect。struts2拦截器相关的类就是切面(Aspect)。完了。什么东东啊!没事。笔者就土话来讲吧。对于传统面向对象编程来讲,执行业务代码一般都会在方法里面。这一点大家都知道。相信大家也知道在执行相关业务代码之前也会执行对应的验证代码。作为一个软件设计师在设计整个构架的时候,一定会理解业务并把业务划分为几个独立的模块。而每模块都一定会有相关的验证代码。如什么数据不能为空等相关验证代码。甚至有一些软件系统希望有对应的日志跟踪的时候,对应的日志代码也要写入进去。如图下。

可以说图上是从纵向角度来看。把业务划分出多个独立业务模块,每一个业务模块都有相应的数据验证和日志记录。笔者认为AOP思想则是要横向角度来看。什么说呢?笔者在上面的图片加入横向角度之后会是一个什么样子。如图下

当笔者把横向角度加入之后,就是会发现把业务模块相关代码和其他代码进行了分离独立起来了。如上面图片中的图1就是加入之后的状态了。那么笔者是什么知道要这样子分呢?主要是关注点。即是横向关注点。笔者希望把业务代码和其他代码进行分离独立起来。这就是笔者的关注点(这里的业务代码是总业务代码。即是把所有的业务代码看作一个整体)。而图片中的图2便是最后的结果。为什么三块验证代码最后变成一个块呢?举个简单的例子吧。笔者相信有工作经验的程序员都会经验过非空的代码或是非空并没有特殊的字符等相关的验证吧。难道你不觉得这块的代码逻辑是可以共通的吗?当然笔者也想过这样子的问题——新一个专门用来实现验证功能的类不就行了吗?没有错。是可以的。可是AOP思想的核心笔者认为不是在这里。他的目标是让业务模块去选择自己对应的验证代码。验证代码就是切面(Aspect)。什么意思呢?当业务模块相关代码和其他代码进行了分离独立的时候。其他代码这个部分事实上是可以进行重组和切分。比如:日志相关的代码变成一个日志切面(只是一个类)。性能相关代码变成一个性能切面。各个切面之间是相互独立的最后就是变成了根据不同的业务模块去选择不同需要的切面。图片上的图2就是最后笔者得出来的结果。当然笔者也不敢说自己是对的。个人看法而以。

对于上面AOP理解也是笔者自己的个人看法而以。笔者也不敢说是对的。每一个人的理解是不一样子的。也不一定见得读者的理解是错的。显然笔者认为AOP真的很不错。纵向把业务划分出模块。横向把代码划出模块。

 拦截器的执行机制

struts2的拦截器笔者认为就是AOP思想的一种体现。在进入action类实例之前必须先执行相关拦截器。即是拦截器相当于AOP思想里面的切面。把用户action类和拦截器分离独立,就像笔者上面讲的横向关注点一样子。因为大部分的用户action类是跟业务有关系的。所以strust2里面有很多拦截器。不同的action类可能会选择不同的拦截器。当然也有一些默认必须有的拦截器。从《Struts2 源码分析——Action代理类的工作》章节里面我们知道执行action请求是在DefaultActionInvocation类的invoke方法。可以这样子讲吧。一切都从这个方法开始的。如下

DefaultActionInvocation类:

 1 public String invoke() throws Exception {
 2         String profileKey = "invoke: ";
 3         try {
 4             UtilTimerStack.push(profileKey);
 5 
 6             if (executed) {
 7                 throw new IllegalStateException("Action has already executed");
 8             }
 9 
10             if (interceptors.hasNext()) {//获得一个拦截器
11                 final InterceptorMapping interceptor = interceptors.next();
12                 String interceptorMsg = "interceptor: " + interceptor.getName();
13                 UtilTimerStack.push(interceptorMsg);
14                 try {
15                     resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);//执行拦截器
16                 } finally {
17                     UtilTimerStack.pop(interceptorMsg);
18                 }
19             } else {
20                 resultCode = invokeActionOnly();
21             }
22 
23             // this is needed because the result will be executed, then control will return to the Interceptor, which will
24             // return above and flow through again
25             if (!executed) {
26                 if (preResultListeners != null) {
27                     LOG.trace("Executing PreResultListeners for result [{}]", result);
28 
29                     for (Object preResultListener : preResultListeners) {
30                         PreResultListener listener = (PreResultListener) preResultListener;
31 
32                         String _profileKey = "preResultListener: ";
33                         try {
34                             UtilTimerStack.push(_profileKey);
35                             listener.beforeResult(this, resultCode);
36                         }
37                         finally {
38                             UtilTimerStack.pop(_profileKey);
39                         }
40                     }
41                 }
42 
43                 // now execute the result, if we're supposed to
44                 if (proxy.getExecuteResult()) {
45                     executeResult();
46                 }
47 
48                 executed = true;
49             }
50 
51             return resultCode;
52         }
53         finally {
54             UtilTimerStack.pop(profileKey);
55         }
56     }

上面的红色的代码是这个方法的核心点之一。也是实现AOP思想的代码亮点。让我们看一下红色代码做什么?判断interceptors是否有拦截器。如果没有就直接执行invokeActionOnly方法。即是执行action类实例对应的方法。如果有就获得拦截器并执行拦截器(执行intercept方法)。好了。关键点就在这个执行拦截器身上。即是执行intercept方法。intercept方法有一个参数就是DefaultActionInvocation类的接口。这个参数让struts2的AOP思想能够进行。为什么这样子讲呢?不清楚读者有没有想过。为什么这边判断拦截器是用if而不是用for 或是 while呢?必竟拦截器不只一个。我们都清楚AOP的目标就是让业务模块选择对应的切面。那么就有可能存在多个拦截器。这也是为什么亮点的原因了。看一下拦截器的代码就知道了。如下

LoggingInterceptor类:

1 public String intercept(ActionInvocation invocation) throws Exception {
2         logMessage(invocation, START_MESSAGE);
3         String result = invocation.invoke();
4         logMessage(invocation, FINISH_MESSAGE);
5         return result;
6     }

上面的源码是笔者从多个拦截器中选择一个比较简单的来看。不清楚你们明白了没有。红色的代码已经很明确说明了一件事情。拦截器开始的时候,执行相关的拦截器逻辑,然后又重新调用DefaultActionInvocation类的invoke方法。从而获得下一个拦截器。就是这样子下一个拦截器又开始执行自己的intercept方法。做了相关的拦截器逻辑之后。又一次重新调用DefaultActionInvocation类的invoke方法。又做了相似的工作。只到没有了拦截器,执行用户action类实例的方法并返回结果。有了结果之后,就开始续继执行当前上一个拦截器的后半部分代码。只到返回到最开始的拦截器执行后半部分的代码。如果硬要说一个相似的专业词语的话。笔者会想到方法叠带。

笔者心里面对这一部分的做法一直很喜欢。当然也不少人不会认同笔者的观念。可以看得出来AOP思想把个个切面和业务模块处理的非常好。切面和业务模块又是独立的互不影响。同时可以让开发人员更加关注对应的业务逻辑。 

注意:学习这一部分最好结合《Struts2 源码分析——核心机制》的核心机制的图片。这样子会更好理解。

本章总结

本章主要是讲到关于拦截器的运行机制。知道了struts2是如果进行处理拦截器和action类实例之间关系。同时也了解了相关AOP思想。

posted @ 2016-09-19 18:14  Aomi  阅读(1400)  评论(0编辑  收藏  举报