Spring Framework是构建与IOC与AOP之上的。这篇文章与另一篇文章SpringIOC的朴素解释是互补的。AOP意为面向切面编程,默认Spring只提供了运行时的方法切面。

1.面向切面编程
面向切面编程(AOP)是面向对象编程(OOP)的补充,它将处理问题的粒度细化到了方法层,在面向对象编程中,最核心的单元是类。它包含以下几个标准,所谓AOP框架就是实现这些标准:
JoinPoint:这是一个运行时概念,JoinPoint包括了运行时该切点的一切信息,可以认为是一个PointCut的具体实现。
Pointcut:就是一个切点定义,代表一类可以织入的规则。
Advice(通知/增强):就是具体的增强逻辑。
Aspect(切面):由Pointcut与Advice组成,它代表一个切面组合。
AopProxy:代理类,这是结果,Aop增强后的结果就是产生一个链接了切点、切面、通知的代理类。
注:还有一些其他的定义这里就不扯了。

在我的很多篇文档都提到了这么一个理念:标准先行。这里的AOP标准就是一个非常非常好的例子。AOP的概念说简单可以简单,说复杂也可以复杂,现在有了标准就可以统一思想实现标准就行了。
代码有三个基本时态:编译时,加载时,运行时。这三个时间都可以人为干预然后增强你的类,比如Lombok就是在编译时增强的,它会在编译时插入增强代码。编译时是不灵活的,并且会让类膨胀。
纯java类库基本上都是在运行时对对象进行操作以达到增强的目的。基本过程是:通过目标对象得到一个增强后的代理对象,使用的时候直接使用代理对象,这个代理对象是与通知列表(Advice)链接在一起的,当调用代理对象的方法时,会调用与该方法(切点,PointCut)相关联的通知,从而达到AOP的目的。所以关键的一步是如何产生合适的代理对象。

2.动态代理与SpringAOP
SpringAOP是通过动态代理实现的,分为两种:JDK动态代理与Cglib代理。实现这一切的基础是在Java中Everything is a object(一切皆对象)。Method是一个对象所以在调用过程中可以被hook插入其他逻辑。Jdk只能对接口进行增强,Cglib则不限接口与类,以下是两个简单的例子:

  • jdk:

    public class JdkProxyTest implements Runnable{
      public static void main(String[] args) {
          JdkProxyTest o = new JdkProxyTest();
          InvocationHandler handler = new TestInvocationHandler(o);
          //Class[]是你要代理的接口,这里也用来提供代理类的类型,同时它也提供需要代理的方法。
          Runnable proxy = (Runnable)Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(), new Class<?>[] {Runnable.class}, handler);
          proxy.run();
      }
      
      public static class TestInvocationHandler implements InvocationHandler{
          
          private Object o;
          
          public TestInvocationHandler(Object o) {
              this.o = o;
          }
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              System.out.println("-----------------pre invoke------------------");
              //调用原来的方法,必须这样搞,不然一直调用代理类的代理方法会死循环。
              Object result = method.invoke(o, args);
              System.out.println("-----------------post invoke--------------------");
              return result;
          }
          
      }
      
      @Override
      public void run() {
          System.out.println("=========do something===============");
      }
    }

    注:Class<?>[]提供类型的同时也提供代理方法,这意味着不仅仅是接口中定义的方法,还有接口继承自Object的方法也会被代理。

  • Cglib:

    public class CglibProxyTest {
      
      public static void main(String[] args) {
           Enhancer e = new Enhancer();
              //父类
           e.setSuperclass(CglibProxyTest.class);
           TestMethodInterceptor mInterceptor = new TestMethodInterceptor();
              //实现接口
           e.setInterfaces(new Class<?>[]{Runnable.class});
    //         e.setCallbackFilter(filter);
              //这是关键一步,是一个Callback回调
           e.setCallback(mInterceptor);
           CglibProxyTest proxy = (CglibProxyTest)e.create();
           proxy.doSome();
      }
      public void doSome() {
          System.out.println("=========do something===============");
      }
      
      public static class TestMethodInterceptor implements MethodInterceptor{
    
          @Override
          public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
              System.out.println("-------------before intercept-----------------");
              Object result = arg3.invokeSuper(arg0, arg2);
              System.out.println("-------------after intercept-----------------");
              return result;
          }
      }
    }

    注:你的Class是不一定要继承某个类,实现某个接口的,他们是用来提供类型与方法的,这一点是与jdk代理不一样的。Cglib依赖ASM库。

Spring对代理类进行了抽象:

AopProxy
├─JdkDynamicAopProxy
└─CglibAopProxy
    └─ObjenesisCglibAopProxy

AopProxy接口只有两个方法:getProxy()和getProxy(ClassLoader)。也就是关键的一步,得到代理对象。而在Spring中能够优雅做到这一点的就是:BeanPostProcessor。BeanPostProcessor在Spring框架的扩展点中非常非常重要,它可以对bean创建进行hook,另外还有一个BeanFactoryPostProcessor也很重要,是对bean注册进行Hook。

使用:

//定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestTarget {}

//应用注解
public class TestClass {
    @TestTarget
    public void doSomething() {
        System.out.println("------------------------------doSomething-----------------------");
    }
}

//切点、切面、通知声明
@Aspect
public class TestAspact {
    @Pointcut("@annotation(com.example.demo.test.TestTarget)")
    public void pointCut() {}
    @Before("pointCut()")
    public void advice(JoinPoint joinPoint) {
        System.out.println("-------------advice----------------");
    }
}

//配置
@Configuration
public class TestConfiguration {    
    @Bean
    public TestClass testClass() {
        return new TestClass();
    }    
    @Bean
    public TestAspact testAspact() {
        return new TestAspact();
    }    
}

上面的所有被注解@TestTarget标识的bean的method都被增强了,增强内容是在控制台输出"-------------advice----------------"。

过程:
这里的过程是Spring使用BeanPostProcessor为bean产生代理对象的过程;也就是SpringAOP过程。这里以上面为的例子:
1.getBean(),这是梦开始的地方就是它调用的BeanPostProcessor方法。
2.AbstractAutoProxyCreator.postProcessAfterInitialization(Object,String),这里的具体实现类是AnnotationAwareAspectJAutoProxyCreator。就是这个BeanPostProcessor对Bean进行了AOP。

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        //得到cacheKey,普通bean就是beanName,FactoryBean则是BeanFactory.FACTORY_BEAN_PREFIX(&)+beanName;
        //如果beanName为空(理论上不可能)则返回Class
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            //如果必要则生成代理
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

3.wrapIfNecessary(),判断是否符合AOP条件,如果符合则产生代理类。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    //已经是缓存过的不需要增强的bean
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    //Advice,Pointcut,Advisor,AopInfrastructureBean不必增强;
    //beanName是.ORIGINAL结尾(这里需要自己看代码,条件必要但是不充分)的不必增强。
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // 关键一步,找到合适的Advice,内部调用findEligibleAdvisors()
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        //缓存,这里可以看出代理类可以再次被增强
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        //关键一步,找到Advice后产生代理类
        Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

注:大概就是俩个关键步骤,a.根据bean信息找到合适的Advice,b.然后为这个bean应用这些advice产生代理对象并返回。

4.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(),这个方法会在所有可用的Advice中找到合适这个Class的advice。

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    //这一步在下面会讲到,它会处理切面
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    //筛选出合适的advisor,
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

注:findAdvisorsThatCanApply()首先设置当前线程正在被代理的beanName,然后内部委托给AopUtils.findAdvisorsThatCanApply(List, Class)找到合适的advice。AopUtils.findAdvisorsThatCanApply()会遍历所有的可用Advisor(candidateAdvisors参数),找到可以应用(AopUtils.canApply(Advisor, Class, boolean))的Advisor存入list并返回。AopUtils.canApply()首先会判断Advisor是IntroductionAdvisor类型还是PointcutAdvisor以应用不同的匹配规则(Introduction可以引入一个目标类中不存在的接口,目前我们都使用Pointcut这个概念,它代表一个切点,可以在切点前后增加逻辑达到增强。);知晓类型后,PointcutAdvisor会取出Pointcut封装信息(这里的实现是AspectJExpressionPointcut),通过这个封装信息匹配是否符合织入条件:a.Pointcut.getClassFilter().matches(Class)会取出PointcutExpression(AspectJExpressionPointcut的属性,具体类型是PointcutExpressionImpl)切点表达式封装,然后这个封装会分析目标类(这里是TestClass)以判断是否合适织入,b.PointCut取出MethodMatcher(这里是它自身this),使用这个methodMatcher匹配目标类(TestClass)的每个方法,如果有一个匹配成功则返回true。

注:Introduction用法:使用Introduction是一种非常非常不好的实践,在别人使用的时候会感觉有些方法是凭空产生的,如果你需要一个接口,那你直接实现就行了,如果这个接口有默认实现你提供接口方法默认实现就行了。当然也许是我没get到它的要点。

//需要引入的接口
public interface TestInterface {
    void doRun();
}
//目标
@Component
public class TestClass {}

//切面
@Component
@Aspect
public class TestAspact {
    //定义切点与默认实现
    @DeclareParents(value = "com.example.demo.test.TestClass" , defaultImpl = DefaultTestInterface.class )
    private TestInterface testInterface;
    //接口默认实现
    public static class DefaultTestInterface implements TestInterface{
        @Override
        public void doRun() {
            System.out.println("-------doRun---------");
        }    
    }
}

//使用
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext app = SpringApplication.run(DemoApplication.class, args);
        //使用
                TestInterface i = (TestInterface)app.getBean("testClass");
        i.doRun();
    }

}

5.AbstractAutoProxyCreator.createProxy()创建代理类。

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
            @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }

    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);

    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }
    //找到所有适合这个bean的Advisor,这里最重要的是会找到通用Advisor,
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);

    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    // 如果Bean类没有在本地加载,则使用原始的ClassLoader来覆盖类加载器。
    ClassLoader classLoader = getProxyClassLoader();
    if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
        classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
    }
    //这里内部会构造AopProxy对象,具体是那个实现会根据proxyFactory设置选择
    //然后通过这个AopProxy对象的getProxy(classLoader)方法得到代理对象
    return proxyFactory.getProxy(classLoader);
}

注:如果目标类是接口或者目标类本身就是代理类型,则产生JdkDynamicAopProxy对象,否则产生ObjenesisCglibAopProxy对象。其还有其它情况(目标类为空或者只设置了接口)会根据ProxyFactory配置选择产生JdkDynamicAopProxy对象。得到AopProxy对象(这里类型是ObjenesisCglibAopProxy)后调用getProxy()得到代理对象,getProxy()如下:

@Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isTraceEnabled()) {
            logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
        }

        try {
            Class<?> rootClass = this.advised.getTargetClass();
            Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

            Class<?> proxySuperClass = rootClass;
            //如果已是代理对象,则拿其父类作为代理父类,其接口添加到接口列表中。
            if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
                proxySuperClass = rootClass.getSuperclass();
                Class<?>[] additionalInterfaces = rootClass.getInterfaces();
                for (Class<?> additionalInterface : additionalInterfaces) {
                    this.advised.addInterface(additionalInterface);
                }
            }

            // 对Class及父类的每个方法的限定符进行校验,只有public或者protected的非静态非final方法能通过校验。
            // 未通过校验的方法会在debug日志级别环境下打印日志。
            validateClassIfNecessary(proxySuperClass, classLoader);

            // 终于到了这一步,构建Enhancer然后配置然后创建代理对象。
            Enhancer enhancer = createEnhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader &&
                        ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
            //这里会找到全部的CallBack回调(实际上是临时new的,它需要根据AopProxy属性的不同而不同)。
            //我们的advisor会放到DynamicAdvisedInterceptor的advisors里面。
            Callback[] callbacks = getCallbacks(rootClass);
            Class<?>[] types = new Class<?>[callbacks.length];
            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
            }
            // fixedInterceptorMap only populated at this point, after getCallbacks call above
            enhancer.setCallbackFilter(new ProxyCallbackFilter(
                    this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
            enhancer.setCallbackTypes(types);

            // 生成代理对象
            return createProxyClassAndInstance(enhancer, callbacks);
        }
        。。。。。。。。。。。。
    }

大概过程就是上面这几个步骤。
这还只是如果生成代理对象的过程,还有一个处理切面的过程:
处理切面也是由AnnotationAwareAspectJAutoProxyCreator完成的,上文中提到的findCandidateAdvisors()会找到所有@Aspect bean用与自动代理。

    protected List<Advisor> findCandidateAdvisors() {
        // Add all the Spring advisors found according to superclass rules.
        List<Advisor> advisors = super.findCandidateAdvisors();
        // Build Advisors for all AspectJ aspects in the bean factory.
        if (this.aspectJAdvisorsBuilder != null) {
            //关键一步buildAspectJAdvisors会处理@Aspect以及其他和AOP相关的注解,找出所有advisor
            advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
        }
        return advisors;
    }

注:buildAspectJAdvisors()第一次调用的时候的时候会将查找容器内所有的Aspect bean并将其处理成Advisor对象然后缓存起来。
注:从这里可以知道,一个Aspect必须要作为bean注入到容器才会生效。
注:具体实现请自行查看BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors()源码。

到这一步SpringAOP的过程就完成了。总的来说它包括两个部分:处理切面和生成代理对象。

3.加载时织入(LoadTime-Weaving)
这部分文档在Spring加载时织入,详细的定义属于Aspectj的知识。这里不打算往下深究了,没多大意义,等我遇到需要加载时织入的时候再来补充,希望你不要这么倒霉有这样的需求。但是可以猜到的是,这里的ClassLoader会使用自定义的ClassLoader。
注:最开始我们提到Spring提供的时运行时的方法AOP,而如果启用了LoadTime-Weaving则可以实现更加精细的AOP。

4.API
Spring使用Advice、Pointcut和Advisor接口分别对通知(advice)、切点(pointcut)和切面(advice)进行抽象。使用如下:

//pointcut切点实现
public class TestPointCut implements Pointcut {

    @Override
    public ClassFilter getClassFilter() {
        return new TestClassFilter();
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return new TestMethodMatcher();
    }

    public static class TestMethodMatcher extends DynamicMethodMatcher{
        @Override
        public boolean matches(Method method, Class<?> targetClass, Object... args) {
            return method.getAnnotation(TestTarget.class) != null;
        }
    }
    
    public static class TestClassFilter implements ClassFilter {

        @Override
        public boolean matches(Class<?> clazz) {
            return TestClass.class.equals(clazz);
        }

    }

}
//advice通知实现
public class TestMethodBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("----------------before-------------------");
    }
    
}
//Advisor切面实现(也就是Aspect)
public class TestAdvisor extends AbstractPointcutAdvisor {
    private static final long serialVersionUID = -5818866045520237089L;
    @Autowired
    private TestPointCut testPointCut;
    @Autowired
    private TestMethodBeforeAdvice testMethodBeforeAdvice;
    @Override
    public Pointcut getPointcut() {
        return testPointCut;
    }
    @Override
    public Advice getAdvice() {
        return testMethodBeforeAdvice;
    }
}
//目标
public class TestClass {
    @TestTarget
    public void doSomething() {
        System.out.println("------------------------------doSomething-----------------------");
    }

}
//配置
@Configuration
public class TestConfiguration {
    @Bean
    public TestClass testClass() {
        return new TestClass();
    }    
    @Bean
    public TestMethodBeforeAdvice testMethodBeforeAdvice() {
        return new TestMethodBeforeAdvice();
    }
    @Bean
    public TestPointCut testPointCut() {
        return new TestPointCut();
    }
    @Bean
    public TestAdvisor testAdvisor() {
        return new TestAdvisor();
    }    
}
//使用
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext app = SpringApplication.run(DemoApplication.class, args);
        TestClass c = app.getBean(TestClass.class);
        c.doSomething();
    }
}

在绝大多数时候我们使用AspectJ提供的表达式就可以实现我们的需求,在不济你可以扩展JdkRegexpMethodPointcut、AspectJExpressionPointcut(AspectJ表达式切点实现)、StaticMethodMatcherPointcut(静态方法匹配切点)、DefaultPointcutAdvisor(默认的切面实现)、MethodXXXXAdvice(方法前置/后置/异常等等等通知,是个接口)等等去满足你要的功能;如果还不能满足再去实现接口。Spring提供了一些工具类比如Pointcuts来辅助完成功能。

还有一些偏门的情况是比如你想自己实现getObject()获取代理类的过程或者所想控制Advisor切面的顺序等等等等一大堆,你可以自己实现ProxyFactoryBean等等,回头遇到再来研究。不过理论上上面的得到代理对象的过程以及涵盖了这部分逻辑。

注:FactoryBean在Spring中是用来获取bean对象(可用的Object)的工厂,他是一个泛型接口。

5.总结
和IOC的文章一样,这篇文章的绝大多是知识是在实际生产中用不上的,也就是无用知识;我依然保持我的观点:最大的问题是如何高效合作,而不是技术过不过关。AOP是有其规范的,有规范就有其实现,会用就行。总的来说,SpringAOP默认提供运行时方法增强,支持AspactJ语法定义,可以实现加载时增强,但是我们确实不到万不得已不应该这么做;SpringAOP包括了俩个过程:切面处理与代理对象生成,这两个步骤都是通过BeanPostProcessor完成的,处理切面是在第一次寻找Advisor的时候完成的,而代理对象生成是在找到合适的Advisor后通过AopProxy得到的;Spring对AOP的概念都抽象了接口并且开放了相关的API。

注:转载请标明出处与作者。如果意见或建议请评论区留言。

标签: springboot, spring

添加新评论