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

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. packagecn.partner4java.springidol;
  2. /**
  3. * 观众需要做的动作
  4. * (就是当调用演唱的时候,把这些动作顺序的加入到演唱的前后)
  5. * @author partner4java
  6. *
  7. */
  8. publicclassAudience {
  9. publicvoidtakeSeats(){
  10. System.out.println("The audience is taking their seats.");
  11. }
  12. publicvoidappluad(){
  13. System.out.println("CLAP CLAP!");
  14. }
  15. publicvoiddemandRefund(){
  16. System.out.println("Boo! We want our moeny back!");
  17. }
  18. }

定义两种实现方式的通知:
方式1:利用前置、后置、异常通知
  1. packagecn.partner4java.springidol;
  2. importjava.lang.reflect.Method;
  3. importorg.springframework.aop.AfterReturningAdvice;
  4. importorg.springframework.aop.MethodBeforeAdvice;
  5. importorg.springframework.aop.ThrowsAdvice;
  6. /**
  7. * Audience 的通知
  8. * 利用前置、后置、异常通知
  9. * @author partner4java
  10. *
  11. */
  12. publicclassAudienceAdviceimplementsMethodBeforeAdvice,
  13. AfterReturningAdvice, ThrowsAdvice {
  14. privateAudience audience;
  15. publicvoidsetAudience(Audience audience) {
  16. this.audience = audience;
  17. }
  18. publicvoidbefore(Method method, Object[] args, Object target)
  19. throwsThrowable {
  20. audience.takeSeats();
  21. }
  22. publicvoidafterReturning(Object returnValue, Method method,
  23. Object[] args, Object target)throwsThrowable {
  24. audience.appluad();
  25. }
  26. //There are not any methods on this interface, as methods are invoked by reflection.
  27. publicvoidafterThrowing(Throwable throwable){
  28. audience.demandRefund();
  29. }
  30. }

方式2:环绕通知
  1. packagecn.partner4java.springidol;
  2. importorg.aopalliance.intercept.MethodInterceptor;
  3. importorg.aopalliance.intercept.MethodInvocation;
  4. /**
  5. * Audience 的环绕通知
  6. * @author partner4java
  7. *
  8. */
  9. publicclassAudienceAroundAdviceimplementsMethodInterceptor {
  10. privateAudience audience;
  11. publicvoidsetAudience(Audience audience) {
  12. this.audience = audience;
  13. }
  14. publicObject invoke(MethodInvocation invocation)throwsThrowable {
  15. try{
  16. audience.takeSeats();
  17. Object returnValue = invocation.proceed();
  18. audience.appluad();
  19. returnreturnValue;
  20. }catch(Exception e) {
  21. audience.demandRefund();
  22. throwe;
  23. }
  24. }
  25. }

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


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


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

调用:
  1. packagecn.partner4java.springidol;
  2. importorg.springframework.context.ApplicationContext;
  3. importorg.springframework.context.support.ClassPathXmlApplicationContext;
  4. publicclassHelloWorld {
  5. publicstaticvoidmain(String[] args) {
  6. ApplicationContext ctx =newClassPathXmlApplicationContext(
  7. "/META-INF/spring/springido.xml");
  8. Performer performer = (Performer) ctx.getBean("duke");
  9. performer.perform();
  10. }
  11. }






















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


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






@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.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="
  6. http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/aop
  9. http://www.springframework.org/schema/aop/spring-aop.xsd">
  10. <bean id="audience"class="cn.partner4java.springidol.Audience"/>
  11. <bean id="performer"class="cn.partner4java.springidol.PerformerBean"></bean>
  12. <aop:config>
  13. <aop:aspect ref="audience">
  14. <aop:pointcut expression="execution(* *.perform*(..))"id="performance"/>
  15. <aop:before method="takeSeats"pointcut-ref="performance"/>
  16. <aop:after-returning method="appluad"pointcut-ref="performance"/>
  17. <aop:after-throwing method="demandRefund"pointcut-ref="performance"/>
  18. </aop:aspect>
  19. </aop:config>
  20. </beans>

调用:
  1. ApplicationContext ctx =newClassPathXmlApplicationContext(
  2. "/META-INF/spring/spring-pojo1.xml");
  3. Performer performer = (Performer) ctx.getBean("performer");
  4. 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. packagecn.partner4java.aspectj;
  2. publicaspect StockServiceAspect {
  3. privateString suffix;
  4. privateString prefix;
  5. publicvoidsetPrefix(String prefix) {
  6. this.prefix = prefix;
  7. }
  8. publicvoidsetSuffix(String suffix) {
  9. this.suffix = suffix;
  10. }
  11. pointcut doServiceCall() :
  12. execution(* cn.partner4java.aspectj.*.*(..));
  13. before() : doServiceCall() {
  14. System.out.println(this.prefix);
  15. }
  16. after() : doServiceCall() {
  17. System.out.println(this.suffix);
  18. }
  19. }

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

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


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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值