转-Spring Framework中的AOP之around通知

Spring Framework中的AOP之around通知

http://blog.csdn.net/xiaoliang_xie/article/details/7049183

标签: springaop设计模式beanintegerclass

2011-12-07 11:39 6108人阅读 评论(0) 收藏 举报

第一部分,您看到了如何使用Spring AOP来实现跟踪和记录方面。跟踪和记录都是“消极”方面,因为它们的出现并不会对应用程序的其他行为产生影响。它们都使用了消极的before和after形式的通知。

  但是如果您希望改变应用程序的常规行为呢?例如说,您希望重写一个方法?这样的话,您就需要使用更积极的around形式的通知。

  第一部分的简单例子应用程序包括IbusinessLogic接口、BusinessLogic类和MainApplication类,如下所示:

[java] view plain copy

  1. public interface IBusinessLogic
  2. {
  3. public void foo();
  4. }
  5. public class BusinessLogic
  6. implements IBusinessLogic
  7. {
  8. public void foo()
  9. {
  10. System.out.println(
  11. "Inside BusinessLogic.foo()");
  12. }
  13. }
  14. import org.springframework.context.ApplicationContext;
  15. import org.springframework.context.support.FileSystemXmlApplicationContext;
  16. public class MainApplication
  17. {
  18. public static void main(String [] args)
  19. {
  20. // Read the configuration file
  21. ApplicationContext ctx =
  22. new FileSystemXmlApplicationContext(
  23. "springconfig.xml");
  24. //Instantiate an object
  25. IBusinessLogic testObject =
  26. (IBusinessLogic) ctx.getBean(
  27. "businesslogicbean");
  28. // Execute the public
  29. // method of the bean
  30. testObject.foo();
  31. }
  32. }

  要对一个BusinessLogic类的实例彻底重写对foo()方法的调用,需要创建around通知,如下面的AroundAdvice类所示:

[java] view plain copy

  1. import org.aopalliance.intercept.MethodInvocation;
  2. import org.aopalliance.intercept.MethodInterceptor;
  3. public class AroundAdvice
  4. implements MethodInterceptor
  5. {
  6. public Object invoke(
  7. MethodInvocation invocation)
  8. throws Throwable
  9. {
  10. System.out.println(
  11. "Hello world! (by " +
  12. this.getClass().getName() +
  13. ")");
  14. return null;
  15. }
  16. }

  要在spring中用作around通知,AroundAdvice类必须实现MethodInterceptor接口和它的invoke(..)方法。每当截获到方法的重写,invoke(..)方法就会被调用。最后一步是改变包含在应用程序的springconfig.xml文件中的Spring运行时配置,以便可以对应用程序应用AroundAdvice。

[java] view plain copy

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE beans PUBLIC  "-//SPRING//DTD BEAN//EN"
  3. "http://www.springframework.org/dtd/spring-beans.dtd">
  4. <beans>
  5. <!-- Bean configuration -->
  6. <bean id="businesslogicbean"
  7. class="org.springframework.aop.framework.ProxyFactoryBean">
  8. <property name="proxyInterfaces">
  9. <value>IBusinessLogic</value>
  10. </property>
  11. <property name="target">
  12. <ref local="beanTarget"/>
  13. </property>
  14. <property name="interceptorNames">
  15. <list>
  16. <value>theAroundAdvisor</value>
  17. </list>
  18. </property>
  19. </bean>
  20. <!-- Bean Classes -->
  21. <bean id="beanTarget"
  22. class="BusinessLogic"/>
  23. <!-- Advisor pointcut definition for around advice -->
  24. <bean id="theAroundAdvisor"
  25. class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
  26. <property name="advice">
  27. <ref local="theAroundAdvice"/>
  28. </property>
  29. <property name="pattern">
  30. <value>.*</value>
  31. </property>
  32. </bean>
  33. <!-- Advice classes -->
  34. <bean id="theAroundAdvice"
  35. class="AroundAdvice"/>
  36. </beans>

  根据该springconfig.xml配置文件,theAroundAdvisor截获所有对BusinessLogic类的方法的调用。接下来,theAroundAdvisor被关联到theAroundAdvice,表明当截获一个方法时,就应该使用在AroundAdvice类中指定的通知。既然已经指定了around通知的正确配置,下一次执行MainApplication类时,BusinessLogic bean的foo()方法就会被截获并重写,如图3所示:


图3. 使用around通知重写对BusinessLogic类中的foo()方法的调用

前面的例子显示,BusinessLogic类中的foo()方法可以通过AroundAdvice类中的invoke(..)方法彻底重写。原来的foo()方法完全不能被invoke(..)方法调用。如果希望从around通知内调用foo()方法,可以使用proceed()方法,可从invoke(..)方法的MethodInvocation参数中得到它。

[java] view plain copy

  1. public class AroundAdvice
  2. implements MethodInterceptor
  3. {
  4. public Object invoke(
  5. MethodInvocation invocation)
  6. throws Throwable
  7. {
  8. System.out.println(
  9. "Hello world! (by " +
  10. this.getClass().getName() +
  11. ")");
  12. invocation.proceed();
  13. System.out.println("Goodbye! (by " +
  14. this.getClass().getName() +
  15. ")");
  16. return null;
  17. }
  18. }

  图4显示了对proceed()的调用如何影响操作的顺序(与图3所示的初始around通知执行相比较)。


图4. 从around通知内使用proceed()调用原来的方法

  当调用proceed()时,实际是在指示被截获的方法(在本例中是foo()方法)利用包含在MethodInvocation对象中的信息运行。您可以通过调用MethodInvocation类中的其他方法来改变该信息。

您可能希望更改包含在MethodInvocation类中的信息,以便在使用proceed()调用被截获的方法之前对被截获方法的参数设置新值。

  通过对MethodInvocation对象调用getArguments()方法,然后在返回的数组中设置其中的一个参数对象,最初传递给被截获的方法的参数可以被更改。

  如果IbusinessClass和BusinessLogic类的foo()方法被更改为使用整型参数,那么就可以将传递给被截获的调用的值由在AroundAdvice的notify(..)方法中传递改为在foo(int)中传递。

[java] view plain copy

  1. public class AroundAdvice
  2. implements MethodInterceptor
  3. {
  4. public Object invoke(
  5. MethodInvocation invocation)
  6. throws Throwable
  7. {
  8. System.out.println(
  9. "Hello world! (by " +
  10. this.getClass().getName() +
  11. ")");
  12. invocation.getArguments()[0] = new Integer(20);
  13. invocation.proceed();
  14. System.out.println(
  15. "Goodbye! (by " +
  16. this.getClass().getName() +
  17. ")");
  18. return null;
  19. }
  20. }

  在本例中,被截获的方法的第一个形参被假设为int。实参本身是作为对象传递的,所以通过将其包装在Integer类实例中的方法,基本的int类型的形参被改为对应数组中的新值。如果您将该参数设置为一个非Integer对象的值,那么在运行时就会抛出IllegalArgumentException异常。

  您还将注意到,invoke(..)方法必须包含一个return语句,因为该方法需要返回值。但是,被重写的foo()方法并不返回对象,所以invoke(..)方法可以以返回null结束。如果在foo()方法不需要的情况下,您仍然返回了一个对象,那么该对象将被忽略。

  如果foo()方法确实需要返回值,那么需要返回一个与foo()方法的初始返回类型在同一个类或其子类中的对象。如果foo()方法返回一个简单类型,例如,一个integer,那么您需要返回一个Integer类的对象,当方法被重写时,该对象会自动由AOP代理拆箱,如图5所示:


图5. around通知的装箱和自动拆箱

  图字:

  Object invoke:对象调用

  The integer return value is boxed in a Integer object in the AroundAdvice and then unboxed by the AOP Proxy:整型返回值被装箱在AroundAdvic通知的一个Integer对象中,然后由AOP代理拆箱。

  面向方面编程还是一个比较新的领域,尤其是与衍生出它的面向对象编程相比。设计模式通常被认为是常见问题的通用解决方案,因为面向方面发展的时间还不长,所以已发现的面向方面设计模式比较少。

  此处要介绍的是一种正在浮现的模式,即Cuckoo‘s Egg设计模式。该模式还有其他的叫法,它在面向对象领域的对等体包括模仿对象(Mock Object)和模仿测试(Mock Testing),甚至代理模式也与它有一些类似之处。

  Cuckoo‘s Egg面向方面设计模式可以被定义为应用程序上下文中功能部件的透明和模块化的置换。就像杜鹃偷偷地把自己的蛋放在另一种鸟的巢中一样,Cuckoo‘s Egg设计模式用一个替代功能部件实现置换现有的功能部件,而使造成的干扰尽可能少。

  这种置换的实现方式可以是静态的、动态的、部分的、完全的,针对一个对象的多个部分,或针对多个组件。使用面向方面的方法可以透明地实现功能部件的置换,而无需对应用程序的其余部分进行更改。要置换应用程序中现有功能部件的替代功能部件就是“杜鹃的蛋”。图6显示了Cuckoo‘s Egg设计模式中的主要组成元素。


图6. Cuckoo‘s Egg设计模式中的主要组成元素

  图字:

  Application:应用程序

  Component:组件

  Replacement Feature:替代功能部件

  Component 1 and 2 together encompass a distinct feature of the software:组件1和2共同包含了软件的一个独立的功能部件

  The Cuckoo‘s Egg pattern transparently replaces an existing feature of the software:Cuckoo‘s Egg模式透明地置换了软件现有的功能部件

  Before the pattern is applied:应用该模式前

  After the pattern is applied:应用该模式后

  Cuckoo‘s Egg设计模式依赖于around通知的概念。您需要借助于积极的和侵入性的around通知来截获并有效置换应用程序中现有的功能部件。

  有关Cuckoo‘s Egg设计模式的更多信息,以及AspectJ中的一个可选实现,请参见《AspectJ Cookbook》(O‘Reilly,2004年12月出版)。

 要使用Spring AOP实现Cuckoo‘s Egg设计模式,需要声明一个around通知来截获所有对要置换的功能部件的调用。与hot-swappable target sources(Spring AOP的一个功能部件,将在本系列的另一篇文章中介绍)不同,around通知的显式使用使得Cuckoo‘s Egg实现可以有效地跨越对象边界(因此也可以跨越bean边界)进行整个功能部件的置换,如图7所示。


图7. 一个跨越bean边界的组件

  图字:

  A feature crosses the boundaries of BusinessLogic and BusinessLogic2 by depending on behavior supplied separately by the two beans:一个功能部件通过依赖于由BusinessLogic和BusinessLogic2各自提供的行为而跨越了这两个bean的边界

  下面的代码显示了一个具有两个bean的简单应用程序,其中有一个功能部件跨越了该应用程序的多个方面。要置换的功能部件可以被视为包含IBusinessLogic bean中的foo()方法和IBusinessLogic2 bean中的bar()方法。IBusinessLogic2 bean中的baz()方法不是 该功能部件的一部分,所以不进行置换。

[java] view plain copy

  1. public interface IBusinessLogic
  2. {
  3. public void foo();
  4. }
  5. public interface IBusinessLogic2
  6. {
  7. public void bar();
  8. public void baz();
  9. }

  此处,ReplacementFeature类扮演了“杜鹃的蛋”的角色,它提供了将被透明地引入应用程序的替代实现。ReplacementFeature类实现了所有在该类引入时要被置换的方法。

[java] view plain copy

  1. public class ReplacementFeature
  2. {
  3. public void foo()
  4. {
  5. System.out.println(
  6. "Inside ReplacementFeature.foo()");
  7. }
  8. public void bar()
  9. {
  10. System.out.println(
  11. "Inside ReplacementFeature.bar()");
  12. }
  13. }

  现在需要声明一个around通知来截获对跨越bean的功能部件的方法调用。CuckoosEgg类提供了某种around通知来检查被截获的方法,并将适当的方法调用传递给ReplacementFeature类的实例。

[java] view plain copy

  1. public class CuckoosEgg implements MethodInterceptor
  2. {
  3. public ReplacementFeature replacementFeature =
  4. new ReplacementFeature();
  5. public Object invoke(MethodInvocation invocation)
  6. throws Throwable
  7. {
  8. if (invocation.getMethod().getName().equals("foo"))
  9. {
  10. replacementFeature.foo();
  11. }
  12. else
  13. {
  14. replacementFeature.bar();
  15. }
  16. return null;
  17. }
  18. }

  因为与Spring框架关系密切,Cuckoo‘s Egg设计的详细信息被放在springconfig.xml配置文件中。对springconfig.xml文件的更改将确保所有对IbusinessLogic和IBusinessLogic2 bean的foo()方法和bar()方法的调用都将被截获,并传递给CuckoosEgg类的around通知。

[html] view plain copy

  1. ...
  2. <!--CONFIG-->
  3. <bean id="businesslogicbean"
  4. class="org.springframework.aop.framework.ProxyFactoryBean">
  5. <property name="proxyInterfaces">
  6. <value>IBusinessLogic</value>
  7. </property>
  8. <property name="target">
  9. <ref local="beanTarget"/>
  10. </property>
  11. <property name="interceptorNames">
  12. <list>
  13. <value>theCuckoosEggAdvisor</value>
  14. </list>
  15. </property>
  16. </bean>
  17. <bean id="businesslogicbean2"
  18. class="org.springframework.aop.framework.ProxyFactoryBean">
  19. <property name="proxyInterfaces">
  20. <value>IBusinessLogic2</value>
  21. </property>
  22. <property name="target">
  23. <ref local="beanTarget2"/>
  24. </property>
  25. <property name="interceptorNames">
  26. <list>
  27. <value>theCuckoosEgg2Advisor</value>
  28. </list>
  29. </property>
  30. </bean>
  31. <!--CLASS-->
  32. <bean id="beanTarget" class="BusinessLogic"/>
  33. <bean id="beanTarget2" class="BusinessLogic2"/>
  34. <!--ADVISOR-->
  35. <bean id="theCuckoosEggAdvisor"
  36. class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
  37. <property name="advice">
  38. <ref local="theReplacementFeaturePart1Advice"/>
  39. </property>
  40. <property name="pattern">
  41. <value>IBusinessLogic.*</value>
  42. </property>
  43. </bean>
  44. <bean id="theCuckoosEgg2Advisor"
  45. class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
  46. <property name="advice">
  47. <ref local="theReplacementFeaturePart2Advice"/>
  48. </property>
  49. <property name="pattern">
  50. <value>IBusinessLogic2.bar*</value>
  51. </property>
  52. </bean>
  53. <!--ADVICE-->
  54. <bean id="theReplacementFeaturePart1Advice" class="CuckoosEgg"/>
  55. <bean id="theReplacementFeaturePart2Advice" class="CuckoosEgg"/>
  56. ...

  当使用修改后的springconfig.xml文件运行例子应用程序时,要替换的、被指定为功能部件的一部分的方法调用完全被截获并传递给ReplacementFeature类。

  通常,即使在同一个实现环境中,我们也可以用不同的方法来实现同一种设计模式。实现上例的另一种方法是实现两个独立的通知。

  最后需要注意的是,使用Cuckoo‘s Egg设计模式置换的功能部件,不管它是跨越bean的还是在一个类中,它的生命周期与它所置换的功能部件的目标生命周期匹配。在上例中这没什么问题,因为只有一个功能部件实例被置换了,而且唯一的Cuckoo‘s Egg通知只维护一个替代功能部件。

  这个例子非常简单,而在实践中,您很可能必须处理大量需要用各自的Cuckoo‘s Egg实例置换的功能部件实例。在这种情况下,单个的方面实例需要被关联到单个的要置换的功能部件实例。本系列的下一篇文章将会考虑方面生命周期的用法,届时将解决这个问题。

  结束语

  本文介绍了如何在Spring框架内谨慎使用around形式的通知。around形式的通知常用于实现Cuckoo‘s Egg设计模式时,所以我们引入了一个例子来说明如何使用Spring AOP实现这种面向方面设计模式。

  在本系列的第三部分中,您将看到如何使用Spring框架中其他的AOP基本概念。这些概念包括:控制方面生命周期、使用基于introduction通知的积极方面改变应用程序的静态结构,以及使用control flow切入点实现对方面编织的更细微的控制。

时间: 2024-10-15 15:18:18

转-Spring Framework中的AOP之around通知的相关文章

[JavaEE - JPA] 3. Spring Framework中的事务管理

前文讨论了事务划分(Transaction Demarcation)在EJB中是如何实现的,本文继续介绍在Spring Framework中是如何完成事务划分的. 我们已经知道了当采用Container事务类型的时候,事务划分主要有以下两种方案(参考这里): 使用JTA接口在应用中编码完成显式划分 在容器的帮助下完成自动划分 在使用JavaEE的EJB规范时,这两种方案分别被实现为BMT以及CMT,关于BMT和CMT在上一篇文章中有比较详尽的讨论(参考这里). 那么对于Spring Framew

Spring Framework------>version4.3.5.RELAESE----->Reference Documentation学习心得----->Spring Framework中的spring web MVC模块

spring framework中的spring web MVC模块 1.概述 spring web mvc是spring框架中的一个模块 spring web mvc实现了web的MVC架构模式,可以被用于开发web网站 spring web mvc 实现web网站的原理,如下图: 2.使用spring web mvc开发web应用的步骤 step1:在自己的工程中引入spring web mvc模块 step2:配置spring web mvc模块 中的DispatcherServlet,告

Spring Boot中使用AOP统一处理Web请求日志

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是Spring框架中的一个重要内容,它通过对既有程序定义一个切入点,然后在其前后切入不同的执行内容,比如常见的有:打开数据库连接/关闭数据库连接.打开事务/关闭事务.记录日志等.基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率.

spring中的AOP 以及各种通知

理解了前面动态代理对象的原理之后,其实还是有很多不足之处,因为如果在项目中有20多个类,每个类有100多个方法都需要判断是不是要开事务,那么方法调用那里会相当麻烦. spring中的AOP很好地解决了这个问题,通过 execution表达式 指定哪些包中的那些类 哪些方法 用到事务 execution(public * *(..))  所有的公共方法 execution(* set*(..))  以set开头的任意方法 execution(* com.xyz.service.AccountSer

Spring 2.0 的AOP介绍及其通知类型

Spring 2.0的AOP 在Spring 2.0中最激动人心的增强之一是关于Spring AOP,它变得更加便于使用而且更加强大,主要是通过复杂而成熟的AspectJ语言的支持功能来实现,而同时保留纯的基于代理的Java运行时.Spring 2.0的AOP提供给我们一种新的思考程序结构的方法,能够解决很多纯OOP无法解决的问题--让我们能够在一个模块中实现某些需求,而不是以发散的方式实现.Spring 2.0允许用户选择使用基于模式或@AspectJ注解的方式来自定义切面.这两种风格都支持所

Spring MVC 中使用AOP 进行统一日志管理--XML配置实现

1.介绍 上一篇博客写了使用AOP进行统一日志管理的注解版实现,今天写一下使用XML配置实现版本,与上篇不同的是上次我们记录的Controller层日志,这次我们记录的是Service层的日志.使用的工程还是原来的那个,具体的Spring mvc 工程搭建暂不介绍.上篇记录controller层日志的时候是将切面类组件叫给spring MVC 进行管理,因为 controller 也是交给spring MVC进行管理的,但是记录service 层日志的时候应该就应该再spring 容器中进行了,

spring mvc中的AOP和interceptors

项目中采用Interceptor来过滤URL来决定哪些可以在不登录的情况下访问,哪些必须要登录才可以访问: public class SessionTimeoutInterceptor implements HandlerInterceptor { 此时需要在servlet.xml中配置<mvc:interceptor> 同时亦采用AOP来记录日志,使用注解方式 @Component@Aspect 同时在servlet.xml中配置 <aop:aspectj-autoproxy>&

Spring boot中使用aop详解

版权声明:本文为博主武伟峰原创文章,转载请注明地址http://blog.csdn.net/tianyaleixiaowu. aop是spring的两大功能模块之一,功能非常强大,为解耦提供了非常优秀的解决方案. 现在就以springboot中aop的使用来了解一下aop. 一:使用aop来完成全局请求日志处理 创建一个springboot的web项目,勾选aop,pom如下: [html] view plain copy print? <?xml version="1.0" e

spring框架中的AOP学习之(一)

一.AOP术语 1.通知(advice):定义了切面何时(@Before, @After, @AfterThrowing ,@AfterReturning,@Around )使用 2.连接点(join point):在应用执行过程中能够被插入切面的一个点. 3.切点(pointcut):对连接点中的哪些连接点进行织入切面(切点是指匹配到的某些连接点) 4.切面(aspect):是通知加切点的给合 5.引入(introduction):允许我们向现有的类添加新的方法和属性(对已有的类引入新的接口,