上一章 详解SSJ(Spring3.x mvc + Spring3.x Core + JPA2.x)轻量级集成开发—第5章 剖析Spring3.x AOP特性01
下一章 详解SSJ(Spring3.x mvc + Spring3.x Core + JPA2.x)轻量级集成开发—第7章 笔者带走进Spring3.x MVC的世界
目录
一、基于Schema风格配置AOP增强处理;
二、基于Schema风格配置全局切入点;
三、Spring AOP与AspectJ AOP的关系;
四、使用@Aspect定义切面;
五、基于Annotation风格配置AOP增强处理;
六、基于Annotation风格配置全局切入点;
前言
笔者在上一章节中,为大家详细且深入的讲解了有关AOP的一些基础知识。那么从本章开始,咱们要开始接着学习有关Spring AOP的后续知识,这些知识包括:增强处理、切入点配置、以及如何使用基于AspectJ的方式配置Spring AOP。但是在学习这些技术之前,笔者还是要提醒各位,掌握基础相对于进阶来说是必经之路,没有一步登天,所以耐下心学习,才能够让你成长的更快。
其次,有些朋友在阅读笔者博文的时候,往往容易误解笔者博文的标题。总是觉得笔者在讲解的时候少了些许后续章节应该包含的东西,在此笔者有必要澄清一些事实。笔者打算对每一章的知识点,逐步分析、逐步讲解,这是需要极其漫长的过程和时间的。所以并不会在某一章节就对所有的知识点进行全方位概括,当然如果是因为博文的标题误导了你,笔者只能对你说声抱歉。
最后还要提及一点的是,由于笔者会经常维护、更新早期博文的内容,所以如果大家有兴趣还是可以再次进行阅读,或许你会有意想不到的收获。
一、详解AOP之增强处理
上一章中,笔者为大家简单的介绍了增强处理的一些概念。还记得什么是增强处理吗?增强处理无非就是定义了通知的执行时机、执行顺序。如果你无法理解什么是执行时机与执行顺序,那么请你回想下咱们在上一章编写JDK动态代理时,是否显式的指定过代理业务的执行?其实增强处理无法就是隐式的帮你做了这些事情而已。在Spring AOP中,增强处理一共包含如下5种类型:
1、前置通知(Before advice):代理业务执行于被拦截的方法之前;
2、后置通知(After advice):代理业务执行于被拦截的方法之后;
3、环绕通知(Around advice):代理业务执行于被拦截的方法之前或之后;
4、异常通知(After throwing advice):代理业务执行于被拦截的方法抛出异常后;
5、返回时通知(After returning advice):代理业务执行于被拦截的方法返回之前;
在基于Schema的AOP中配置中,我们可以通过使用如下标签配置Spring AOP的增强处理:
1、<aop:before/>:用于配置前置通知(Before advice);
2、<aop:after/>:用于配置后置通知(After advice);
3、<aop:around/>:用于配置环绕通知(Around advice);
4、<aop:after-throwing/>:用于配置异常通知(After throwing advice);
5、<aop:after-returning/>:用于配置返回通知(After returning advice);
上述标签我们可以将其称之为增强处理标签,因为这些标签均带有部分相同属性。分别为:method、pointcut、pointcut-ref、throwing和returning。其中属性“method”用于指定作为增强处理类型的切面类的指定方法。属性“pointcut”允许定义一个表达式,该表达式的作用就是拦截指定切入点,并且该表达式支持通配符“*”和“..”。其中通配符“*”代表了任意命名模式的匹配,而“..”则代表了接受0—N个任意类型参数的方法。属性“pointcut-ref”用于引用一个已经存在的切入点名称,换句话来说,该属性可以用于引用一个全局拦截切入点表达式。属性“throwing”仅针对<aop:after-throwing/>标签有效,该属性用于指定一个参数,异常通知(After throwing advice)便可以通过这个参数访问委托对象的指定方法抛出的异常。属性“returning”同样仅针对<aop:after-returning/>标签有效,该属性用于指定一个参数,返回时通知(After returning advice)便可以通过这个参数访问委托对象的指定方法的返回值。
笔者打算先从前置通知(Before advice)和后置通知(After advice)开始讲起,因为这两个通知是Spring AOP增强处理中最为简单,同时也是最容易理解的。前置通知(Before advice)无非就是在切入点执行之前首先执行代理业务,而后置通知(After advice)则反之。
基于Schema的风格配置Before and After增强处理:
<aop:config> <aop:aspect id="logAspect" ref="logBean" order="1"> <!-- 前置通知 --> <aop:before method="logTest" pointcut="execution(* org.johngao.bean.LoginBean.*(..))" /> <!-- 后置通知 --> <aop:after method="logTest" pointcut="execution(* org.johngao.bean.LoginBean.*(..))" /> </aop:aspect> </aop:config> <bean name="logBean" class="org.johngao.bean.LogBean" />
在上述配置文件中,笔者为切入点定义了2个通知,分别为前置通知(Before advice)和后置通知(After advice)。在切入点执行之前首先执行前置通知(Before advice),然后再执行切入点,最后执行后置通知(After advice)。其中属性“pointcut”的值为:"execution(* org.johngao.bean.LoginBean.*(..))" 。也就是说指定通知将拦截org.johngao.bean包下LoginBean类型的所有带参或无参方法。当你看到这里的时候,或许你已经明白,在一个切入点内部,允许同时定义多个通知。
Before and After通知实现:
/** * 模拟前置通知、后置通知 * * @author JohnGao * * @param JoinPoint: 连接点 * * @return void */ public void logTest(JoinPoint joinPoint) { /* 获取委托对象指定方法参数 */ Object[] params = joinPoint.getArgs(); for(Object param: params) System.out.println(param); /* 获取委托对象指定方法名称 */ System.out.println(joinPoint.getSignature().getName()); /* 获取委托对象实例 */ joinPoint.getTarget(); /* 获取代理对象实例 */ joinPoint.getThis(); System.out.println("日志记录..."); }
在上述程序示例中,笔者使用到了AspectJ提供的JoinPoint接口。开发人员使用JoinPoint接口可以很方便的访问到委托对象的上下文信息,并且在实际开发过程中,这些上下文信息对于开发人员而言至关重要。JoinPoint接口的常用方法如下:
方法名称 | 方法返回值 | 方法描述 |
getArgs() | Object[] | 获取委托对象的目标方法参数列表,注意:由于返回数组所以需要拆分 |
getSignature() | Signature | 获取委托对象的方法签名 |
getTarget() | Object | 获取委托对象实例 |
getThis() | Object | 获取代理对象实例 |
当然JoinPoint接口还派生有另一个常用的扩展接口,那便是ProceedingJoinPoint接口。该接口仅限用于环绕通知(Around advice),并且相对JoinPoint接口而言,ProceedingJoinPoint接口为开发人员提供有2个新增用于执行委托对象的方法,分别为:
方法名称 | 方法返回值 | 方法描述 |
proceed() | Object | 用于执行委托对象的目标方法 |
proceed(java.lang.Object[] args) | Object | 用于执行委托对象的目标方法,用新参数替换原先入参 |
至于如何使用ProceedingJoinPoint接口,大家目前不必着急,笔者在后续章节自然会进行讲解。为了更加形象的描述不同通知的执行时机与执行顺序,笔者接下来会为大家展示各种通知执行时序图。
Before and After增强处理执行时序图:
当大家理解前置通知(Before advice)和后置通知(After advice)后,笔者接下来将会为大家讲解异常通知(After throwing advice)的配置和使用。所谓异常通知(After throwing advice)无非就是在切入点抛出异常之后执行代理业务,Spring的增强处理为开发人员提供了多种环境下执行通知的机会,在此笔者不得不佩服Rod Johnson丰富的想象力及“创造力”。
基于Schema的风格配置After throwing增强处理:
<aop:config> <aop:aspect id="logAspect" ref="logBean" order="1"> <!-- 异常通知 --> <aop:after-throwing method="logTest" pointcut="execution(* org.johngao.bean.LoginBean.*(..))" /> </aop:aspect> </aop:config> <bean name="logBean" class="org.johngao.bean.LogBean" />
如果你定义的切入点并没有往外抛出异常,通知是无法执行的。所以使用异常通知(After throwing advice)的时候,我们务必需要将为切入点throws异常。
还记得笔者在上一章节提到过的throwing属性吗?其仅针对<aop:after-throwing/>标签有效,该属性用于指定一个参数,异常通知(After throwing advice)便可以通过这个参数访问委托对象的指定方法抛出的异常。使用throwing属性指定参数:
<aop:after-throwing method="logTest" pointcut="execution(* org.johngao.bean.LoginBean.*(..))" throwing="exception" />
捕获委托对象的指定方法抛出的异常:
/** * 模拟异常通知 * * @author JohnGao * * @param JoinPoint: 连接点, Exception: 异常信息 * * @return void */ public void logTest(JoinPoint joinPoint, Exception exception) { System.out.println(exception); }
通过上述程序示例我们可以看出,一旦委托对象的目标方法抛出异常后,我们便可以在通知中通过属性“exception”访问所抛出的异常信息。当然定义在通知中的方法参数,Spring的IOC容器自然会负责其初始化工作,所以你无须关心这些“琐事”,尽可能的关注于你的业务既可。
After throwing advice增强处理执行时序图:
返回时通知(After returning advice)其实也比较简单,该通知执行于被拦截的方法返回之前。也就是说当返回时通知(After returning advice)拦截切入点后,应当首先执行切入点,最后切入点即将返回时再执行通知。
基于Schema的风格配置After returning增强处理:
<aop:config> <aop:aspect id="logAspect" ref="logBean" order="2"> <!-- 返回时通知 --> <aop:after-returning method="logTest" pointcut="execution(* org.johngao.bean.LoginBean.*(..))" /> </aop:aspect> </aop:config> <bean name="logBean" class="org.johngao.bean.LogBean" />
还记得笔者在上一章节提到过的returning属性吗?其仅针对<aop:after-returning/>标签有效,该属性用于指定一个参数,返回时通知(After returning advice)便可以通过这个参数访问委托对象的指定方法的返回值。使用returning属性指定参数:
<aop:after-returning method="logTest" pointcut="execution(* org.johngao.bean.LoginBean.*(..))" returning="rvt" />
获取委托对象的目标方法返回值:
/** * 模拟返回时通知 * * @author JohnGao * * @param JoinPoint: 连接点, Object: 返回值信息 * * @return void */ public void logTest(JoinPoint joinPoint, Object rvt) { System.out.println(rvt); }
通过上述程序示例我们可以看出,如果需要获取委托对象的目标方法的返回值时,我们可以通过定义<aop:after-returning/>标签中的“returning”属性即可。但这里需要注意的是,returning属性的属性名称务必和通知方法中的属性名称保持一致。
After returning advice增强处理执行时序图:
笔者至此已经为大家讲解了前置(Before advice)、后置(After advice)、异常(After throwing advice)、返回时(After returning advice)等4种通知的配置和使用,那么接下来笔者就开始讲解环绕通知(Around advice)。至于为什么需要将环绕通知(Around advice)放在最后一个讲解?并不是因为它相对其他几个通知更复杂,而是因为这个通知更特殊。
但从字面上的理解,很多开发人员会错误的将环绕通知(Around advice)理解为是前置通知(Before advice)和后置通知(After advice)的结合体。以为该通知无非就是在切入点的执行前后各执行一次,其实这是错误的,笔者希望大家不要因为字面的显浅理解就妄下定论,免得贻笑大方。
环绕通知(Around advice)适用的场景更多的基于并发环境,并且该通知可以满足于切入点之前或者之后执行,甚至可以根据实际业务来判断是否执行。这才是使用环绕通知(Around advice)真正的目的。
基于Schema的风格配置Around增强处理:
<aop:config> <aop:aspect id="logAspect" ref="logBean" order="1"> <!-- 环绕通知 --> <aop:around method="logTest" pointcut="execution(* org.johngao.bean.LoginBean.*(..))" /> </aop:aspect> </aop:config> <bean name="logBean" class="org.johngao.bean.LogBean" />
ProceedingJoinPoint接口派生于JoinPoint接口,该接口仅限于环绕通知(Around advice)获取委托对象的上下文信息使用。笔者刚才也提到过,在实际开发过程中,我们可以使用环绕通知(Around advice)根据实际业务,决定通知是否执行,以及何时执行。
Around通知实现:
/** * 模拟环绕通知 * * @author JohnGao * * @param ProceedingJoinPoint: 连接点 * * @return void */ public void logTest(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("日志记录..."); /* 执行委托对象的目标方法 */ proceedingJoinPoint.proceed(); System.out.println("日志记录..."); }
ProceedingJoinPoint接口的proceed()方法用于执行委托对象的目标方法。然而ProceedingJoinPoint接口还提供有另外一个带参的proceed(java.lang.Object[] args)方法,该方法除了可以用于执行委托对象的目标方法外,还支持使用新参数替换原先入参。
使用proceed(java.lang.Object[] args)方法替换原先入参:
/* 执行委托对象的目标方法 */ proceedingJoinPoint.proceed(new Object[]{"新入参,替换原先入参"});
一旦使用proceed(java.lang.Object[] args)方法替换原先入参后,新参数将会 覆盖之前传递给委托对象的目标方法参数。当然如果你不希望替换原先入参,笔者建议你还是使用proceed()方法即可。