分享

Spring高级程序设计 6 Spring AOP 进阶

 KILLKISS 2012-09-28
1AOP简介 
通知(Advice):通知定义了切面是什么以及何时使用。除了要描述切面要完成的工作,通知还解决了何时执行这个工作的问题。 
连接点(Joinpoint):连接点是在程序执行过程中能够插入切面的一个点。这个店可以是方法被调用时、异常被抛出时、甚至字段被编辑时。切面代码可以通过这些点插入到程序的一般流程之中,从而添加新的行为。 
切入点(Poincut):切入点可以缩小切面通知的连接点的范围。如果说通知定义了切面的“什么”和“何时”,那么切入点就定义了“何地”。 
切面(Aspect):切面是通知和切入点的组合。 
引入(Introduction):“引入”允许我们向现有的类添加新方法或者属性。 
目标(Target):被通知的对象。 
代理(Proxy):是向目标对象应用通知之后被创建的对象。 
织入(Weaving):是把切面应用到目标对象来创建新的代理对象的过程。编译时、类加载时、运行时。 



主流的Spring: 
AspectJ\JBoss AOP\Spring AOP 


Spring对AOP的支持: 
经典的基于代理的AOP(各版本Spring); 
@AspectJ注解驱动的切面。(仅Spring2.0); 
纯POJO切面(仅Spring2.0); 
注入式AspectJ切面(各版本Spring)。
 


Spring通知是用Java编写的: 
Spring创建的全部通知都是用标准的Java编写的。 

Spring在运行时通知对象: 
Spring利用代理类包裹切面,从而把他们注入到Spring管理的Bean里。 

Spring只支持方法连接点: 
由于Spring是基于动态代理的,他只支持方法连接点。 





2创建典型的Spring切面 

Spring AOP的通知类型: 
Before(前):org.springframework.aop.MethodBeforeAdvice 
After-returning(返回后):org.springframework.aop.AfterReturningAdvice 
After-throwing(抛出后):org.springframework.aop.ThrowsAdvice 
Around(包围):org.aopalliance.intercept.MethodInterceptor 
Introduction(引入):org.springframework.aop.IntroductionInterceptor
 

除了Around之外,这些接口都属于Spring。 




demo: 
通知调用的方法类
  1. package cn.partner4java.springidol;  
  2.   
  3. /** 
  4. * 观众需要做的动作 
  5. * (就是当调用演唱的时候,把这些动作顺序的加入到演唱的前后) 
  6. * @author partner4java 
  7. * 
  8. */  
  9. public class Audience {  
  10.     public void takeSeats(){  
  11.         System.out.println("The audience is taking their seats.");  
  12.     }  
  13.       
  14.     public void appluad(){  
  15.         System.out.println("CLAP CLAP!");  
  16.     }  
  17.       
  18.     public void demandRefund(){  
  19.         System.out.println("Boo! We want our moeny back!");  
  20.     }  
  21.       
  22. }  

定义两种实现方式的通知: 
方式1:利用前置、后置、异常通知
  1. package cn.partner4java.springidol;  
  2.   
  3. import java.lang.reflect.Method;  
  4.   
  5. import org.springframework.aop.AfterReturningAdvice;  
  6. import org.springframework.aop.MethodBeforeAdvice;  
  7. import org.springframework.aop.ThrowsAdvice;  
  8.   
  9.   
  10. /** 
  11. * Audience 的通知 
  12. * 利用前置、后置、异常通知 
  13. * @author partner4java 
  14. * 
  15. */  
  16. public class AudienceAdvice implements MethodBeforeAdvice,  
  17.         AfterReturningAdvice, ThrowsAdvice {  
  18.     private Audience audience;  
  19.   
  20.     public void setAudience(Audience audience) {  
  21.         this.audience = audience;  
  22.     }  
  23.   
  24.     public void before(Method method, Object[] args, Object target)  
  25.         throws Throwable {  
  26.         audience.takeSeats();  
  27.     }     
  28.       
  29.     public void afterReturning(Object returnValue, Method method,  
  30.             Object[] args, Object target) throws Throwable {  
  31.         audience.appluad();  
  32.     }  
  33.   
  34.   
  35.     //There are not any methods on this interface, as methods are invoked by reflection.  
  36.     public void afterThrowing(Throwable throwable){  
  37.         audience.demandRefund();  
  38.     }  
  39.       
  40. }  

方式2:环绕通知
  1. package cn.partner4java.springidol;  
  2.   
  3. import org.aopalliance.intercept.MethodInterceptor;  
  4. import org.aopalliance.intercept.MethodInvocation;  
  5.   
  6. /** 
  7. * Audience 的环绕通知 
  8. * @author partner4java 
  9. * 
  10. */  
  11. public class AudienceAroundAdvice implements MethodInterceptor {  
  12.     private Audience audience;  
  13.   
  14.     public void setAudience(Audience audience) {  
  15.         this.audience = audience;  
  16.     }  
  17.   
  18.     public Object invoke(MethodInvocation invocation) throws Throwable {  
  19.         try {  
  20.             audience.takeSeats();  
  21.             Object returnValue = invocation.proceed();  
  22.             audience.appluad();  
  23.               
  24.             return returnValue;  
  25.         } catch (Exception e) {  
  26.             audience.demandRefund();  
  27.             throw e;  
  28.         }  
  29.     }  
  30.   
  31. }  

定义切点和通知者: 
声明正则表达式切点、联合切点与通知者:
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www./schema/beans"  
  3.        xmlns:xsi="http://www./2001/XMLSchema-instance"  
  4.        xmlns:aop="http://www./schema/aop"  
  5.        xsi:schemaLocation="  
  6.             http://www./schema/beans  
  7.             http://www./schema/beans/spring-beans.xsd  
  8.             http://www./schema/aop  
  9.             http://www./schema/aop/spring-aop.xsd">  
  10.   
  11.     <bean id="audience" class="cn.partner4java.springidol.Audience"/>  
  12.       
  13.     <!-- 通知(Advice):通知定义了切面是什么以及何时使用 -->  
  14.     <bean id="advice1" class="cn.partner4java.springidol.AudienceAdvice">  
  15.         <property name="audience" ref="audience"></property>  
  16.     </bean>  
  17.     <bean id="advice2" class="cn.partner4java.springidol.AudienceAroundAdvice">  
  18.         <property name="audience" ref="audience"></property>  
  19.     </bean>  
  20.       
  21.       
  22.     <!-- 切入点(Poincut):切入点可以缩小切面通知的连接点的范围 -->  
  23.     <bean id="performancePointcut1" class="org.springframework.aop.support.JdkRegexpMethodPointcut">  
  24.         <property name="pattern" value=".*perform"></property>  
  25.     </bean>  
  26.     <!-- 定义AspectJ方式切点 -->  
  27.     <bean id="performancePointcut2" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">  
  28.         <property name="expression" value="execution(* Performer+.perform(..))"></property>  
  29.     </bean>  
  30.       
  31.       
  32.     <!-- 切面(Aspect):切面是通知和切入点的组合  (通知者)-->  
  33.     <bean id="audienceAdvisor1" class="org.springframework.aop.support.DefaultPointcutAdvisor">  
  34.         <property name="advice" ref="advice1"></property>  
  35.         <property name="pointcut" ref="performancePointcut1"></property>  
  36.     </bean>  
  37.     <bean id="audienceAdvisor2" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">  
  38.         <property name="advice" ref="advice1"></property>  
  39.         <property name="pattern" value=".*perform"></property>  
  40.     </bean>  
  41.       
  42.     <bean id="performer" class="cn.partner4java.springidol.PerformerBean"></bean>  
  43.           
  44. </beans>  


定义AspectJ切点 
正则表达式虽然可以作为切点定义语言来使用,但他并不是针对切点而创建的,其主要用途换是文本解析。与之相比,从AspectJ里定义切点的方式就可以看出AspectJ的切点语言是一种真正的切点表达式语言。 


使用ProxyFactoryBean: 
代理(Proxy):是向目标对象应用通知之后被创建的对象 
利用ProxyFactoryBean代理被切面的对象。
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www./schema/beans"  
  3.        xmlns:xsi="http://www./2001/XMLSchema-instance"  
  4.        xmlns:aop="http://www./schema/aop"  
  5.        xsi:schemaLocation="  
  6.             http://www./schema/beans  
  7.             http://www./schema/beans/spring-beans.xsd  
  8.             http://www./schema/aop  
  9.             http://www./schema/aop/spring-aop.xsd">  
  10.   
  11.     <bean id="audience" class="cn.partner4java.springidol.Audience"/>  
  12.       
  13.     <!-- 通知(Advice):通知定义了切面是什么以及何时使用 -->  
  14.     <bean id="advice1" class="cn.partner4java.springidol.AudienceAdvice">  
  15.         <property name="audience" ref="audience"></property>  
  16.     </bean>  
  17.     <bean id="advice2" class="cn.partner4java.springidol.AudienceAroundAdvice">  
  18.         <property name="audience" ref="audience"></property>  
  19.     </bean>  
  20.       
  21.       
  22.     <!-- 切入点(Poincut):切入点可以缩小切面通知的连接点的范围 -->  
  23.     <bean id="performancePointcut1" class="org.springframework.aop.support.JdkRegexpMethodPointcut">  
  24.         <property name="pattern" value=".*perform"></property>  
  25.     </bean>  
  26.     <!-- 定义AspectJ方式切点 -->  
  27.     <bean id="performancePointcut2" class="org.springframework.aop.aspectj.AspectJExpressionPointcut">  
  28.         <property name="expression" value="execution(* Performer+.perform(..))"></property>  
  29.     </bean>  
  30.       
  31.       
  32.     <!-- 切面(Aspect):切面是通知和切入点的组合  (通知者)-->  
  33.     <bean id="audienceAdvisor1" class="org.springframework.aop.support.DefaultPointcutAdvisor">  
  34.         <property name="advice" ref="advice1"></property>  
  35.         <property name="pointcut" ref="performancePointcut1"></property>  
  36.     </bean>  
  37.     <bean id="audienceAdvisor2" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">  
  38.         <property name="advice" ref="advice1"></property>  
  39.         <property name="pattern" value=".*perform"></property>  
  40.     </bean>  
  41.       
  42.       
  43.     <!-- 使用ProxyFactoryBean -->  
  44.     <bean id="performer" class="cn.partner4java.springidol.PerformerBean"></bean>  
  45.       
  46.     <bean id="duke" class="org.springframework.aop.framework.ProxyFactoryBean">  
  47.         <property name="target" ref="performer"></property>  
  48.         <property name="interceptorNames" value="audienceAdvisor1"></property>  
  49.         <property name="proxyInterfaces" value="cn.partner4java.springidol.Performer"></property>  
  50.     </bean>  
  51.       
  52. </beans>  

调用:
  1. package cn.partner4java.springidol;  
  2.   
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5.   
  6. public class HelloWorld {  
  7.   
  8.     public static void main(String[] args) {  
  9.         ApplicationContext ctx = new ClassPathXmlApplicationContext(  
  10.                     "/META-INF/spring/springido.xml");  
  11.           
  12.         Performer performer = (Performer) ctx.getBean("duke");  
  13.         performer.perform();  
  14.     }  
  15.   
  16. }  






















3自动代理 
@AspectJ注解 
@AspectJ跟AspectJ没有关系。他是Spring用来解析连接点和通知的一组java 5注解。
 

demo1:
  1. package cn.partner4java.spring.aspectj;  
  2.   
  3. import org.aspectj.lang.ProceedingJoinPoint;  
  4. import org.aspectj.lang.annotation.Around;  
  5. import org.aspectj.lang.annotation.Aspect;  
  6.   
  7.   
  8. /** 
  9. * 通过@Aspect注解实现日志记录 
  10. * @author partner4java 
  11. * 
  12. */  
  13. @Aspect  
  14. public class LoggingAspect {  
  15.   
  16.     @Around("execution(* cn.partner4java.spring.aspectj.*.*(..))")  
  17.     public Object log(ProceedingJoinPoint joinPoint) throws Throwable {  
  18.         System.out.println("begin");  
  19.         Object ret = joinPoint.proceed();  
  20.         System.out.println("after");  
  21.         return ret;  
  22.     }  
  23. }  
  1. package cn.partner4java.spring.aspectj;  
  2.   
  3. /** 
  4. * 被测试的bean 
  5. * @author partner4java 
  6. * 
  7. */  
  8. public class TestBean {  
  9.     public void work() {  
  10.         System.out.println("work");  
  11.     }  
  12.   
  13.     public void stop() {  
  14.         System.out.println("stop");  
  15.     }  
  16. }  
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www./schema/beans"  
  3.        xmlns:xsi="http://www./2001/XMLSchema-instance"  
  4.        xmlns:aop="http://www./schema/aop"  
  5.        xsi:schemaLocation="  
  6.             http://www./schema/beans  
  7.             http://www./schema/beans/spring-beans.xsd  
  8.             http://www./schema/aop  
  9.             http://www./schema/aop/spring-aop.xsd">  
  10.   
  11.     <bean id="test" class="cn.partner4java.spring.aspectj.TestBean"/>  
  12.     <bean class="cn.partner4java.spring.aspectj.LoggingAspect">  
  13.     </bean>  
  14.     <aop:aspectj-autoproxy />  
  15.   
  16. </beans>  
  1. package cn.partner4java.spring.aspectj;  
  2.   
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5.   
  6. public class LoggingAspectDemo {  
  7.   
  8.     public static void main(String[] args) {  
  9.         ApplicationContext ac = new ClassPathXmlApplicationContext(  
  10.                 "/META-INF/spring/ataspectjdemo1-context.xml"  
  11.         );  
  12.         TestBean testBean = (TestBean) ac.getBean("test");  
  13.         testBean.work();  
  14.         testBean.stop();  
  15. //        后台打印:  
  16. //        begin  
  17. //        work  
  18. //        after  
  19. //        begin  
  20. //        stop  
  21. //        after  
  22.     }  
  23.   
  24. }  






@AspectJ方面详解 
切入点声明表达式可以引入别人的表达式如: 
@Pointcut("execution(* com.apress.prospring2.ch06.simple.TestBean.*(..))") 
private void testBeanExecution() { } 

@Around("testBeanExecution()") 
public Object log(ProceedingJoinPoint pjp) throws Throwable { 

或者 

@Around("SystemPointcuts.testBeanExecution()") 
public Object log(ProceedingJoinPoint pjp) throws Throwable { 



切入点表达式: 
execution:匹配方法执行连接点。我们可以指定包、类或者方法名,以及方法的可见性、返回值和参数类型。这是应用的最为广泛的切入点表达式。 
within:匹配那些在已声明的类型中执行的连接点。 
this:通过用bean引用的类型跟指定的类型做对比来匹配连接点。 
args:通过比较方法的参数类型跟指定的参数类型来匹配连接点。 
@target:通过检查调用目标对象是否具有特定注解来匹配连接点。 
@args:跟args类似,不过@args检查的是方法参数的注解而不是他们的类型。 
@within:跟within相似,这个表达式匹配那些带有特定注解的类中执行的连接点。 
@annotation:通过检查讲被调用的方法上的注解是否为指定的注解来匹配连接点。 
bean:通过比较bean的ID来匹配连接点,我们也可以在bean名模式中使用通配符。 


我们可以使用||(或)和&&(与)组合切入点表达式。!(非)取否。 




探讨切入点表达式: 
1、execution表达式 
如: 
* com.partner4java..*.*(..) 
第一个*表示任意返回类型,..*表示包下的所有类,*(..)表示任意方法和任意参数. 


2、within表达式 
within(com..TestBean):匹配从TestBean的方法中产生的调用 
within(com..*):匹配在com包及其子包的任意类中对任意方法调用的执行过程的切入点。 


3、this表达式 
this表达式和within表达有些类似,但是不能使用..或者*等通配符。 
this(class-name) 


4、target表达式 
和this完全一样。 

5、args表达式 
args(type-pattern?(,type-pattern)*) 
可以使用..通配符 
execution(* SimpleBean.*(String,String)) 
args(Integer,..,String) 


6、@target表达式 













4定义纯粹的POJO切面 
Spring开发组意识到使用ProxyFactoryBean有些欠优雅,所以致力于提供一种更好的切面声明方式。Spring 2.0里新的XML配置元素就体现出了这种努力。 


Spring 2.0的AOP配置元素: 
<aop:advisor>:定义一个AOP通知者 
<aop:after>:定义一个AOP后通知(不考虑被通知的方法是否成功返回) 
<aop:after-returning>:定义一个AOP返回后通知 
<aop:after-throwing>:定义一个AOP抛出后通知 
<aop:around>:定义一个AOP包围通知 
<aop:aspect>:定义一个切面 
<aop:before>:顶一个AOP前置通知 
<aop:config>:顶级AOP元素。大多数<aop:*>元素必须包含在 
<aop:pointcut>:定义一个切点 


demo: 
使用上面的元素来定义一个完整的切面,其中包含通知者和切入点,不同的是,我们不需要自己声明代理(Proxy)就完成了织入(Weaving)。
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www./schema/beans"  
  3.        xmlns:xsi="http://www./2001/XMLSchema-instance"  
  4.        xmlns:aop="http://www./schema/aop"  
  5.        xsi:schemaLocation="  
  6.             http://www./schema/beans  
  7.             http://www./schema/beans/spring-beans.xsd  
  8.             http://www./schema/aop  
  9.             http://www./schema/aop/spring-aop.xsd">  
  10.   
  11.     <bean id="audience" class="cn.partner4java.springidol.Audience"/>  
  12.       
  13.     <bean id="performer" class="cn.partner4java.springidol.PerformerBean"></bean>  
  14.       
  15.     <aop:config>  
  16.         <aop:aspect ref="audience">  
  17.             <aop:pointcut expression="execution(* *.perform*(..))" id="performance"/>  
  18.             <aop:before method="takeSeats" pointcut-ref="performance"/>  
  19.             <aop:after-returning method="appluad" pointcut-ref="performance"/>  
  20.             <aop:after-throwing method="demandRefund" pointcut-ref="performance"/>  
  21.         </aop:aspect>  
  22.     </aop:config>  
  23. </beans>  

调用:
  1. ApplicationContext ctx = new ClassPathXmlApplicationContext(  
  2.                 "/META-INF/spring/spring-pojo1.xml");  
  3.   
  4. Performer performer = (Performer) ctx.getBean("performer");  
  5. performer.perform();  



















5注入AspectJ切面 

虽然Spring AOP对于大多数切面程序来说就足够了,但是与AspectJ相比,他只能算是一个功能较弱的AOP解决方案。AspectJ提供了Spring AOP不可能实现的多种切点类型。 

(如果想更深入的了解AspectJ,可以阅读Raminvas Ladded的AspectJ in Action(Manning,2003)) 


demo: 
创建第一个AspectJ方面。
 
Spring通过添加aspectOf()方法增加对AspectJ的支持,不需要你额外的管理容器。 
创建一个aj格式的文件,也就是AspectJ的切面:
  1. package cn.partner4java.aspectj;  
  2.   
  3. public aspect StockServiceAspect {  
  4.     private String suffix;  
  5.     private String prefix;  
  6.   
  7.     public void setPrefix(String prefix) {  
  8.         this.prefix = prefix;  
  9.     }  
  10.   
  11.     public void setSuffix(String suffix) {  
  12.         this.suffix = suffix;  
  13.     }  
  14.   
  15.     pointcut doServiceCall() :  
  16.         execution(* cn.partner4java.aspectj.*.*(..));  
  17.   
  18.     before() : doServiceCall() {  
  19.         System.out.println(this.prefix);  
  20.     }  
  21.   
  22.     after() : doServiceCall() {  
  23.         System.out.println(this.suffix);  
  24.     }  
  25. }  

配置文件:额外添加factory-method="aspectOf"知名是AspectJ文件
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www./schema/beans"  
  3.        xmlns:xsi="http://www./2001/XMLSchema-instance"  
  4.        xsi:schemaLocation="  
  5.             http://www./schema/beans  
  6.             http://www./schema/beans/spring-beans.xsd">  
  7.   
  8.     <bean id="stockService" class="cn.partner4java.aspectj.DefaultStockService"/>  
  9.     <!-- 额外添加factory-method="aspectOf"知名是AspectJ文件 -->  
  10.     <bean class="cn.partner4java.aspectj.StockServiceAspect"  
  11.         factory-method="aspectOf">  
  12.         <property name="prefix" value="Before call"/>  
  13.         <property name="suffix" value="After call"/>  
  14.     </bean>  
  15.   
  16. </beans>  

调用:为了让示例程序正常运行,我们必须使用AspectJ编译器(需要像使用JDK那样安装,配置环境变量等)
  1. package cn.partner4java.aspectj;  
  2.   
  3. import org.springframework.context.ApplicationContext;  
  4. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  5.   
  6.   
  7. public class HelloWorld {  
  8.   
  9.     public static void main(String[] args) {  
  10.         //为了让示例程序正常运行,我们必须使用AspectJ编译器  
  11.         ApplicationContext ac = new ClassPathXmlApplicationContext(  
  12.                 "/META-INF/spring/aspectjdemo1-context.xml"  
  13.         );  
  14.         StockService stockService = (StockService) ac.getBean("stockService");  
  15.         System.out.println(stockService.getStockLevel("ABC"));  
  16.     }  
  17.   
  18. }  


AspectJ还可以指定“加载时织入”: 
使用spring-agent.jar作为JVM代理来替代<aop:aspectj-autoproxy/>,并使用上下文命名空间来初始化加载时织入。 
除了ApplicationContext的XML配置文件之外,还需要创建META-INF/aop.xml文件。这个aop.xml文件是一个标准的AspectJ组件。他告诉AspectJ注入器在加载时织入哪些类。 
Spring加入<context:load-time-weaver>,可以配置aspectj-weaving,设置on或者off设置开启或关闭织入。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多