Spring AOP的织入

各个模块我们已经实现好了,剩下的工作,就是拼装各个模块。

要进行织入,AspectJ采用ajc编译器作为它的织入器;JBoss AOP使用自定义的ClassLoader作为它的织入器;而在SpringAOP中,使用类org.springframework.aop.framework.ProxyFactory作为织入器。

如何与ProxyFactory打交道

首先需要声明的是,ProxyFactory并非SpringAOP中唯一可用的织入器,而是最基本的一个织入器实现,所以,我们就从最基本的这个织入器开始,来窥探一下SpringAOP的织入过程到底是一个什么样子。

使用ProxyFactory来进行横切逻辑的织入很简单。我们知道,SpringAOP是基于代理模式的AOP实现,织入过程完成后,会返回织入了横切逻辑的目标对象的代理对象。为ProxyFactory提供必要的“生产原材料”之后,ProxyFactory就会返回那个织入完成的代理对象(如以下代码所示):

 
 
 
    ProxyFactory weaver = new ProxyFactory(yourTargetObject);
    // 或者
    // ProxyFactory weaver = new ProxyFactory();
    // weaver.setTarget(task);
 
    Advisor advisor = ...;
    weaver.addAdvisor(advisor);
    Object proxyObject = weaver.getProxy();
    // 现在可以使用proxyObject了
 

使用ProxyFactory只需要指定如下两个最基本的东西。

  • 第一个是要对其进行织入的目标对象。我们可以通过ProxyFactory的构造方法直接传入,也可以在ProxyFactory构造完成之后,通过相应的setter方法进行设置。

  • 第二个是将要应用到目标对象的Aspect。哦,在Spring里面叫做Advisor。不过,除了可以指定相应的Advisor之外,还可以使用weaver.addAdvice(...);来直接指定各种类型的Advice。

对于Introduction之外的Advice类型,ProxyFactory内部就会为这些Advice构造相应的Advisor,只不过在为它们构造的Advisor中使用的Pointcut为Pointcut.TRUE,即这些“没穿衣服”的Advice将被应用到系统中所有可识别的Joinpoint处;

而如果添加的Advice类型是Introduction类型,则会根据该Introduction的具体类型进行区分:如果是IntroductionInfo的子类实现,因为它本身包含了必要的描述信息,框架内部会为其构造一个DefaultIntroductionAdvisor;而如果是DynamicIntroductionAdvice的子类实现,框架内部将抛出AopConfigException异常(因为无法从DynamicIntroductionAdvice取得必要的目标对象信息)。

但是,在不同的应用场景下,我们可以指定更多ProxyFactory的控制属性,以便让ProxyFactory帮我们生成必要的代理对象。我们知道,SpringAOP在使用代理模式实现AOP的过程中采用了动态代理和CGLIB两种机制,分别对实现了某些接口的目标类和没有实现任何接口的目标类进行代理,所以,在使用ProxyFactory对目标类进行代理的时候,会通过ProxyFactory的某些行为控制属性对这两种情况进行区分。

在继续下面内容之前,有必要先设定一个简单的场景,以便大家结合实际情况来查看和分析在不同场景下,ProxyFactory在使用方式上的细微差异。假设我们的目标类型定义如下:

 
 
    public interface ITask {
      void execute(TaskExecutionContext ctx);
    }
 
 
 
    public class MockTask implements ITask {
      public void execute(TaskExcutionContext ctx) {
        System.out.println("task executed.");
      }
    }

有了要拦截的目标类,还得有织入到Joinpoint处的横切逻辑,也就是要用到某个Advice实现。我们就把之前的PerformanceMethodInterceptor先拿来一用(见下方代码)。

 
 
    public class PerformanceMethodInterceptor implements MethodInterceptor {
      private final Log logger = LogFactory.getLog(this.getClass());
 
      public Object invoke(MethodInvocation invocation) throws Throwable {
        StopWatch watch = new StopWatch();
        try {
          watch.start();
          return invocation.proceed();
        } finally {
          watch.stop();
          logger.info(watch.toString);
        }
      }
    }
 

有了这些之后,让我们来看一下使用ProxyFactory对实现了ITask接口的目标类,以及没有实现任何接口的目标类如何进行代理。

1. 基于接口的代理

MockTask实现了ITask接口,要对这种实现了某些接口的目标类进行代理,我们可以为ProxyFactory明确指定代理的接口类型,如下所示:

 
 
    MockTask task = new MockTask();
    ProxyFactory weaver = new ProxyFactory(task);
 
    weaver.setInterface(new Class[]{ITask.class});
 
    NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
    advisor.setMappedName("execute");
    advisor.setAdvice(new PerformanceMethodIntercepotr());
    weaver.addAdvisor(advisor);
    ITask proxyObject = (ITask)weaver.getProxy();
    proxyObject.execute(null);
 

通过setInterfaces()方法可以明确告知ProxyFactory,我们要对ITask接口类型进行代理。另外,在这里,我们通过NameMatchMethodPointcutAdvisor来指定Pointcut和相应的Advice(Perfor- manceMethodInterceptor)。至于什么类型的Pointcut、Advice以及Advisor,我们完全可以根据个人的喜好或者具体场景来使用,举一反三嘛!

不过,如果没有其他行为属性的千预,我们也可以不使用setInterfaces()方法明确指定具体的接口类型。这样,默认情况下,ProxyFactory只要检测到目标类实现了相应的接口,也会对目标类进行基于接口的代理,如下所示:

 
    MockTask task = new MockTask();
    ProxyFactory weaver = new ProxyFactory(task);
 
    NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
    advisor.setMappedName("execute");
    advisor.setAdvice(new PerformanceMethodIntercepotr());
    weaver.addAdvisor(advisor);
    ITask proxyObject = (ITask)weaver.getProxy();
    proxyObject.execute(null);

简单点儿说,如果目标类实现了至少一个接口,不管我们有没有通过ProxyFactorysetInterIfaces()方法明确指定要对特定的接口类型进行代理,只要不将ProxyFactoryoptimizeproxyTargetClass两个属性的值设置为true(这两个属性稍后将谈到),那么ProxyFactory都会按照面向接口进行代理。

2. 基于类的代理

如果目标类没有实现任何接口,那么,默认情况下,ProxyFactory会对目标类进行基于类的代理,即使用CGLIB。假设我们现在有一个对象,定义如下:

    public class Executable {
      public void execute() {
        System.out.println("Executable without any Interfaces");
      }
    }
 

如果使用Executable作为目标对象类,那么,ProxyFactory就会对其进行基于类的代理,如下代码演示了使用ProxyFactoryExecutable进行织入的过程:

 
 
    ProxyFactory weaver = new ProxyFactory(new Executable());
 
    NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
    advisor.setMappedName("execute");
    advisor.setAdvice(new PerformanceMethodIntercepotr());
    weaver.addAdvisor(advisor);
 
    Executable proxyObject = (Executable)weaver.getProxy();
    proxyObject.execute();
    System.out.println(proxyObject.getClass());
 

从输出结果可以看出来,最终的代理对象是基于CGLIB的:

image-20220409131330038

但是,即使目标对象类实现了至少一个接口,我们也可以通过proxyTargetClass属性强制ProxyFactory采用基于类的代理。

MockTask为例,它实现了ITask接口,默认情况下ProxyFactory对其会采用基于接口的代理,但是,通过proxyTargetClass,我们可以改变这种默认行为(见如下代码):

 
 
    ProxyFactory weaver = new ProxyFactory(new Executable());
    // 重点
    waver.setProxyTargetClass(true);
 
    NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
    advisor.setMappedName("execute");
    advisor.setAdvice(new PerformanceMethodIntercepotr());
    weaver.addAdvisor(advisor);
 
    MockTask proxyObject = (MockTask)weaver.getProxy();
    proxyObject.execute();
    System.out.println(proxyObject.getClass());

除此之外,如果将ProxyFactoryoptimize属性设定为true的话,ProxyFactory也会采用基于类的代理机制。关于optimize属性的更多信息,我们将在后面给出。

总地来说,如果满足以下列出的三种情况中的任何一种,ProxyFactory 将对目标类进行基于类的代理

  • 如果目标类没有实现任何接口,不管proxyTargetClass的值是什么,ProxyFactory会采用基于类的代理。

  • 如果ProxyFactory的proxyTargetClass属性值被设置为true,ProxyFactory会采用基于类的代理。

  • 如果ProxyFactory的optimize属性值被设置为true,ProxyFactory会采用基于类的代理。

3. Introduction的织入

不做介绍 有兴趣可以看原文

看清ProxyFactory的本质

不做介绍 有兴趣可以看原文

容器中的织入器——ProxyFactoryBean

不做介绍 有兴趣可以看原文

加快织入的自动化进程

不做介绍 有兴趣可以看原文

TargetSource

通常,在使用ProxyFactory的时候,我们都是通过setTarget()方法指定具体的目标对象。使用ProxyFactoryBean也是如此,或者ProxyFactoryBean还可以通过setTargetName()指定目标对象在IoC容器中的bean定义名称。

但除此之外,还有一种方式没有说,那就是还可以通过setTargetSource()来指定目标对象。

不做更多介绍 有兴趣可以看原文

本章小结

本章我们详尽剖析了SpringAOP中的各种概念和实现原理,这些概念和实现原理是SpringAOP发布之初就确定的,是整个框架的基础。纵使框架版本如何升级,甚至为SpringAOP加入更多的特性,在升级和加入更多更多特性的过程中,也将一直秉承SpringAOP的这些理念。

了解SpringAOP框架发布之初就确立的各种概念和原理,可以帮助我们更好地理解和使用SpringAOP。甚至,可以帮助我们去扩展SpringAOP。而接下来要讲述的,就是Spring2.0之后对SpringAOP进行的扩展。