第10章 Spring AOP二世

本章内容

  • @AspectJ形式的Spring AOP
  • 基于Schema的AOP

@AspectJ形式的Spring AOP

Spring框架2.0版本发布之后,SpringAOP增加了新的特性,或者说增加了新的使用方式。

  • 支持AspectJ5发布的@AspectJ形式的AOP实现方式。现在,我们可以直接使用POJO来定义Aspect以及相关的Advice,并使用一套标准的注解标注这些POJO。SpringAOP会根据注解信息查找相关的Aspect定义,并将其声明的横切逻辑织入当前系统。

  • 简化了的XML配置方式 。现在,使用新的基于XSD的配置方式,我们可以使用aop独有的命名空间,并且注册和使用POJO形式实现的AOP概念实体。因为引入了AspectJ的Pointcut描述语言,也可以在新的配置方式中使用AspectJ形式的Pointcut表达式。但这只是从使用的角度来看。

如果从更“本质”一点儿的角度进行分析的话,我们会发现当升级到2.0版本之后,实际上如下两点是最主要的。

  • 可以使用POJO声明Aspect和相关的Advice ,而再也不用像1.x版本中那样,要实现特定的Advice就需要实现规定的接口。

  • 获得了新的Pointcut表述方式 ,因为现在引入了AspectJ的Pointcut表述语言,再也不用在“直接指定方法名”还是“使用正则表达式”之间选择了。至于说基于XSD的简化的配置方式,应该算是锦上添花之作。

虽然2.0之后的SpringAOP集成了AspectJ,但实际上只能说是仅仅拿来AspectJ的“皮大衣”用一下。而底层各种概念的实现以及织入方式,依然使用的是Spring1.x原先的实现体系。这就好像我们中国人说中国话,而英语在世界上较为普及并且有范围较广的影响力,我们可以学习英语,把英语拿过来为我所用,但本质上,我们还是中国人,而不是英国人。换句话说,SpringAOP还是SpringAOP,只不过多学了门外语而己。下面让我们看一下当SpringAOP拥有了AspectJ这种表达能力之后,同样的话该怎么来说吧!

@AspectJ代表一种定义Aspect的风格,它让我们能够以POJO的形式定义Aspect,没有其他接口定义限制。唯一需要的,就是使用相应的注解标注这些Aspect定义的POJO类。之后,SpringAOP会根据标注的注解搜索这些Aspect定义类,然后将其织入系统。

这种方式是从AspectJ所引入的,定义的Aspect类基本上可以在SpringAOP和AspectJ之间通用。不过,SpringAOP只使用AspectJ的类库进行Pointcut的解析和匹配,最终的实现机制还是SpringAOP最初的架构,也就是使用代理模式处理横切逻辑的织入。

下面我们来看看@AspectJ形式是如何使用的!

@AspectJ形式AOP使用之先睹为快

如果将之前的PerformanceMethodInterceptor定义的横切逻辑以@Aspect形式实现,首先得定义一个Aspect,以最普通的POJO来定义这个Aspect就可以。

按照@AspectJ形式重构后的PerformanceMethoaInterceptor定义,如下方代码所示。

 
 
    @Aspect
    public class PerformanceTraceAspect {
        private final Log logger = LogFactory.getLog(PerformanceTraceAspect.class);
 
        @pointcut("executlon(public void * .method1()) ll executlon(pub1ic voia * .method2())")
        public void pointcutName() {
        }
 
        @Around("pointcutName()")
        public ObjectperformanceTrace(ProceedingJoinPoint joinpoint) throws Throwable {
            StopWatch watch = new StopWatch();
            try {
                watch.start();
                return joinpoint.proceed();
            } finally {
                watch.stop();
                logger.info("PT in method[" + joinpoint.getSignature().getName()
                    + "]>>>>> " + watch.toString());
            }
        }
    }
 

定义这么一个Aspect,我们再也无需像1.x时代的SpringAOP那样实现相应的接口了,现在唯一要做的就是为这个Aspect类加上一个Aspect的注解。这样,稍后我们可以根据这个@Aspect,来判断Classpath中哪些类是我们要找的Aspect定义。

我们知道,Aspect中可以定义多个Pointcut以及多个Advice,所以,除了要使用@Aspect标注Aspect类之外,还需要通过名为@Pointcut的注解指定Pointcut定义,通过@Around等注解来指定哪些方法定义了相应的Advice逻辑。

至于说这些注解如何使用,以及对应的方法定义还有什么需要注意的地方,我们先不要管,稍后会详细讲述。

假设我们有如下目标对象类定义:

 
    public class Foo {
      public void method1() {
        System.out.println("method1 execution");
      }
 
      public void method2() {
        System.out.println("method2 execution");
      }
    }

现在有两种方式将Aspect定义织入这个目标对象类,实现对其符合Pointcut定义的Joinpoint(也就是方法执行)进行拦截。

1. 编程方式织入

还记得在讲解 ProxyFactory 的时候,除了 ProxyFactoryBean,我们还提到 ProxyFactory 的另一个“兄弟”吗?对,那就是 org.springframework.aop.aspectj.annotation.AspectJProxyFactory。 通过 AspectJProxyFactory,我们就可以实现 Aspect 定义到目标对象的织入,这样就有了如下代码所示的编程方式织入过程:

 
 
    AspectJProxyFactory weaver = new AspectJProxyFactory();
    weaver.setProxyTargetClass(true);
    weaver.setTarget(new Foo());
    weaver.addAspect(PerformanceTraceAspect.class);
 
    Object proxy = weaver.getProxy();
    ((Foo)proxy).method1();
    ((Foo)proxy).method2();

AspectJProxyFactory的使用与ProxyFactory没有多大差别,只不过多了addAspect()方法,通过该方法可以直接为AspectJProxyFactory添加相应的Aspect定义。实际上,如果我们愿意,完全可以把AspectJProxyFactory当作ProxyFactory来用!

2. 通过自动代理织入

针对@AspectJ风格的AOP,SpringAOP专门提供了一个AutoProxyCreator实现类进行自动代理,以免去过多编码和配置的工作,这个AutoProxyCreator我们之前也提到过,即org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator。它是在AbstractAdvisorAutoProxyCreator的基础上给出的一个扩展类,它的直接父类是AspectJAwareAdvisorAutoProxyCreator。与使用其他AutoProxyCreator一样,我们只需要在IoC容器的配置文件中注册一下AnnotationAwareAspectJAutoProxyCreator就可以了,如下配置所示:

 
    <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">
      <property name="proxyTargetClass" value="true"></property>
    </bean>
 
    <bean id="performanceAspect" class="org.darrenstudio.books.unveilspring.aop.aspectj.PerformanceTraceAspect"/>
 
    <bean id="target" class="...Foo">
    </bean>

现在,AnnotationAwareAspectJAutoProxyCreator会自动搜集IoC容器中注册的Aspect,并应用到Pointcut定义的各个目标对象上。如果我们通过容器取得现在的target对象的话,会发现它已经是被代理过的了,如下:

 
 
    ApplicationContext ctx = new ClassPathXmlApplicationContext("...");
    Object proxy = ctx.getBean("target");
    ((Foo)proxy).method1();
    ((Foo)proxy).method2();
 

当然,如果把target作为依赖对象注入其他的bean定义,那么依赖的主体对象现在持有的也是被代理过的目标对象。

刚才AnnotationAwareAspectJAutoProxyCreator注册到容器的方式是基于DTD的配置方式,在Spring1x以及2.x版本中都可以使用。如果我们能够使用Spring2.x版本,并且使用基于XSD的配置方式,还可以有另一种更加简洁的配置方式,如下方代码所示。

image-20220409172718359

通过面向aop命名空间的<aop:aspectj-autoproxy>,可以达到与基于DTD的配置方式中,直接声明AnnotationAwareAspectJAutoProxyCreator相同的效果。

该元素背后的工作实际上就是由AnnotationAwareAspectJAutoProxyCreator来做的。另外,不要忘了将aop命名空间的Schema定义引入XSD定义。

@AspectJ形式的Pointcut

暂时不做介绍,后面可能会补上

@AspectJ形式的Advice

@AspectJ形式的Advice定义,实际上就是使用@Aspect标注的Aspect定义类中的普通方法。只不过,这些方法需要针对不同的Advice类型使用对应的注解进行标注。

可以用于标注对应Advice定义方法的注解包括:

  • @Before。用于标注Before Advice定义所在的方法;

  • @AfterReturning。用于标注After Returning Advice定义所在的方法;

  • @AfterThrowing。用于标注After Throwing Advice定义所在的方法,也就是在SpringAOP中称为ThrowsAdvice的那种Advice类型;

  • @After。用于标注After(finally) Advice定义所在的方法,1.x版本的SpringAOP中没有对应这种类型的Advice接口定义或者实现;

  • @Around。用于标注AroundAdvice定义所在的方法,也就是常说的拦截器类型的Advice;

  • @DeclareParents。用于标注Introduction类型的Advice,但该注解对应标注对象的域(Field),而不是方法(Method)。

更多不做介绍,后面可能会补上