Spring-Aop

111 阅读9分钟

Spring-Aop

分析Aop的大体实现的思路,说明关键类的作用。

例子

  1. 配置

    package com.lc.proxy;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @EnableAspectJAutoProxy
    public class ProxyConfig {
    	@Bean
    	public ProxyBean proxyBean() {
    		return new ProxyBean();
    	}
    
    	@Bean
    	public TestAdvice testAdvice() {
    		return new TestAdvice();
    	}
    }
    

    @EnableAspectJAutoProxy是开启Aop的关键,他本质就是一个@Import,导入了AspectJAutoProxyRegistrar,下面会分析

  2. 目标类

    package com.lc.proxy;
    
    
    public class TestBean {
    	@ProxyAnnotation(name = "teste")
    	public String sayHello(String name){
    		System.out.println("ProxyBean.sayHello");
    		return "sayHello";
    	}
    }
    
  3. 切面

    package com.lc.proxy;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    
    @Aspect
    @Component
    public class TestAspect {
    
    	@Pointcut("@annotation(ProxyAnnotation)")
    	public void jointPoint() {
    	}
    
    	@Before("jointPoint()")
    	public void before() {
    		System.out.println("before");
    	}
    
    	@After("jointPoint()")
    	public void after() {
    		System.out.println("after");
    	}
    
    	@AfterReturning(value = "jointPoint()", returning = "result")
    	public void afterReturning(Object result) {
    		System.out.println("afterReturning");
    	}
    
    	@AfterThrowing(value = "jointPoint()", throwing = "ex")
    	public void afterThrowing(Exception ex) {
    		System.out.println("afterReturning");
    	}
    
    	@Around("jointPoint()")
    	public Object around(ProceedingJoinPoint pjp) {
    		Object result = null;
    		try {
    			System.out.println("around-before");
    			result = pjp.proceed();
    			System.out.println("around-after");
    		} catch (Throwable throwable) {
    			throwable.printStackTrace();
    		}
    		return result;
    	}
    }
    

    这里配置多个为了等会验证多个切面作用于一个切入点时候的顺序问题。

  4. 注解

    package com.lc.proxy;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface ProxyAnnotation {
    	String name() default "";
    }
    
  5. 测试类

    package com.lc.proxy;
    
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class TestProxyApplication {
    	public static void main(String[] args) {
    		try {
    			AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProxyConfig.class);
    			TestBean bean = context.getBean(TestBean.class);
    			bean.sayHello("test");
    		} catch (Throwable e) {
    			e.printStackTrace();
    		}
    	}
    }
    
  6. 结果

image-20220227095336516.png

其实从这个结果也可以看出来,多个切面作用于同一个切入点的顺序是,@Around,@Before,@After,@AfterReturning,@AfterThrowing。

如果是同一种类型的切面,比如说两个被@Before修饰的,他俩就按照方法名来比较,其实就是String字符串的比较了。

分析

EnableAspectJAutoProxy

配置类上面的@EnableAspectJAutoProxy注解干了什么事情?

这是Spring的这种老操作了, 比如@EnableTransaction就是通过ImportBeanDefinitionRegistrarImportSelect这种机制往Spring容器里面导入Bean,比如Springboot的自动装配就是通过加载ImportSelect的机制,通过META-INF/spring.factories,加载自动配置类导入到Spring容器中。

@EnableAspectJAutoProxy往Spring容器中导入了AnnotationAwareAspectJAutoProxyCreator

通过他可以读取Spring容器中和AspectJ注解相关的一些Bean,从而组装为Advisor,之后在通过ProxyFactory来创建代理对象。从而实现AOP。

image-20220227101535041.png

上面的类图已经说的很清楚了,大体就三部分,Spring中Bean的生命周期,代理对象的配置。作为AbstractAutoProxyCreator及其子类来说,需要做的就是在Bean的生命周期的某个环节组装好创建代理对象需要的配置信息(目标类,Advisor,Advice,接口等等)。在某个环节创建好代理对象就好了。

属性

image-20220227101900678.png

它里面的这俩属性,其实间接对应的是ProxyConfig中的两个属性。

image-20220227102242949.png

他俩是在Spring选择代理对象的时候可以用到。

  1. proxyTargetClass:在选择代理对象的创建方式的时候用(cglib创建还是JDK动态代理创建。true就会去尝试走Cglib代理方式,要是目标类是一个接口或者已经被Jdk动态代理创建了,还是会走JDK的动态代理),这也是尝试的意思。
  2. exposeProxy:是否要将代理对象暴露出去(其实就是创建了代理对象之后,调用目标方法的时候,就会调用到创建代理对象的回调方法上面,true:就会将当前的代理对象放在一个ThreadLocal里面,名字叫做AopContext

那么下面就有个几个问题

  1. 是在Spring Bean生命周期的那个环节来组装好代理需要创建代理对象的配置的(Advisor)?
  2. 是在Spring Bean生命周期的哪个环节来根据这些配置来创建代理对象的?

继续往下看

组装创建代理对象的配置信息。

这里的组装分为两个部分。

  • 找到所有的Advisor。

  • 在所有的Advisor里面找到通过AspectJ来做匹配,找出满足条件的Advisor。

找到所有的Advisor

Advisor只能找一次,得有缓存,并且还得有一个缓存来存放那些Bean可以创建代理对象,那些不行。不能每次创建一个Bean都去判断一次吧。

  1. 在Spring bean生命周期的那个环节找呢?

InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation(Class<?>,String),具体对应的实现类是AbstractAutoProxyCreator,postProcessBeforeInstantiation的目的是为了在真正的创建Spring bean之前,给一个机会,可以创建代理对象,如果返回的不是null,表示对象已经创建结束,不会走Spring标准的创建Bean的生命周期。但是得注意,这个并不是Spring创自己创建代理对象的地方,他是让我们可以自己创建的。通过(TargetSourceCreator)。可以通过对应的set方法设置进去。但是一般好像都是null

  1. 怎么找出所有的Advisor的?

    其实就是怎么把被@Aspect标注的类,还有对应那几个注解标注的方法封装为Advisor的。

    主要的逻辑是拿到Spring容器中所有的Bean,通过class对象获取他的元信息,找到@Aspect标注的类,在解析方法,找到对应注解的标注的方法,然后将这些方法按照指定的顺序排序(就例子中说的切面的顺序)。将一个个的注解对应的方法封装为Advisor对象。放在缓存里面。但是要注意,这里并没有判断这个Advisor是否符合切入点。(也就是说,这里只是将@Aspect标注的类封装为Advisor)

    但是这里也规定一些规则,有一些Bean是不能创建代理对象的。如下图所示:

image-20220227111644068.png

还有Aspect注解标注的类。

  1. 代码分析

    对应代码在AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors()

    他重写了父类的方法,先看父类

    父类是通过BeanFactoryAdvisorRetrievalHelper(只是用来从BeanFactory中检索Advisor的实现类的,在没有什么功能了,他主要的目的就是为解耦,将检索Advisor的逻辑封装在一个Bean里面)来从Spring容器中找出所有的Advisor的实现类。

    子类作用范围比他更大,因为子类是处理注解相关的。所以就得拿到所有的Bean,遍历判断,创建Advisor。

    对比父类的检索方式,这里肯定也需要一个类来帮助它做检索(BeanFactoryAspectJAdvisorsBuilder(从BeanFactory中检索@Aspect注解标注的方法))此外,还需要创建Advisor,在Advisor创建的时候还得需要@Aspect注解标注的类(还得需要元信息,可能还得创建,虽然在他是在BeanFactory中的,但是得有一个他的获取的方式)。所以,这种创建的操作就得工厂类。

    1. MetadataAwareAspectInstanceFactory

      创建@Aspect注解标注的类的,此外还能创建它的元信息。@Aspect标注的类肯定是在BeanFactory中,所以,他里面肯定有一个属性是BeanFactory。元信息,就是类上面有什么注解呀类似的这种信息。

    2. AspectJAdvisorFactory

      创建Advisor的,它创建Advisor是需要MetadataAwareAspectInstanceFactory因为MetadataAwareAspectInstanceFactory代表的是一个被@Aspect标注的类的元信息和实例,而Advisor是从这些被@Aspect标注的方法派生出来的。

image-20220227113614027.png

创建出来的Advisor是什么样子?

InstantiationModelAwarePointcutAdvisorImpl

image-20220227151753352.png

所以,在创建Advisor的时候,需要PointCut(切入点),还有Advice(通知)。

这里的切入点肯定是AspectJ风格的。通过不同的注解来创建不同的Advice,其实就是先拿到@Aspect标注的类,拿到里面被@Around,@Before,@After,@AfterReturning,@AfterThrowing修饰的方法,在创建对应的Advice。如果注意到这些注解所在的包的话,你会发现,他们都不是Spring的包。

创建的PointCut具体的类是什么?

具体的代码在 ReflectiveAspectJAdvisorFactory#getPointcut(Method,Class):AspectJExpressionPointcut里面。

image-20220227153308089.png

怎么来创建Advice的?

是创建InstantiationModelAwarePointcutAdvisorImpl的时候在它的构造函数里面初始化Advice的。

具体的代码在InstantiationModelAwarePointcutAdvisorImpl#instantiateAdvice中,在他里面调用了AspectJAdvisorFactory#getAdvice(Method,AspectJExpressionPointcut,MetadataAwareAspectInstanceFactory,int,String):Advice方法。

方法参数说明:

  1. Method:就是标注通知注解的那个方法。

  2. AspectJExpressionPointcut:pointCut表达式,上一步创建好的。

  3. MetadataAwareAspectInstanceFactory:获取被@Aspect注解标注的类的信息和实例的工厂类。

  4. int:顺序,默认都是0

  5. String:名字,被@Aspect标注的类的名字。

    主要是找到注解的属性,验证,通过不同的类型来创建不同的Advice。

image-20220227154350333.png

这里看一个@Around对应的Advice。

image-20220227154904801.png

他实现直接实现了MethodInterceptor接口,在之前说过,代理对象创建的时候,会将代理对象的调用和Adivce封装为MethodInvocation对象,通过他来调用。当我们调用MethodInvocation.proceed方法的时候,会判断Advice有没有调用结束,如果调用结束了,就调用目标类的方法,否则继续调用Advice,并且会将MethodInvocation传递进去。

image-20220227155442451.png

先确定调用通知类方法的参数,在通过AspectInstanceFactory来创建通知类,从而调用通知方法。对于环绕通知类说,我们都是要调用MethodInvocation.proceed,这样他才能走到下一步。关于MethodInvocation可以看(ReflectiveMethodInvocation#proceed(),关于这个创建代理对象的逻辑,之前的文章里分析过)。

到这,所有的Advisor已经创建好了。

在所有的Advisor里面找到通过AspectJ来做匹配,找出满足条件的Advisor。

上一步找出所有的Advisor之后,这里就要做匹配了,但是他是在Spring Bean的哪个环节呢?

是在BeanPostProcessor#postProcessAfterInitialization(Object,String):void

他本就是Spring 创建好了Bean之后的回调方法,所以,在这里创建是最合适的,在他之前依赖注入也搞定了。

具体的代码看AbstractAutoProxyCreator#postProcessAfterInitialization的实现。

主要的逻辑,遍历所有的Advisor,获取PointCut,遍历这个Bean里面所有的方法。通过PointCut做匹配

具体的代码在AopUtils#findAdvisorsThatCanApply(List<Advisor>, Class<?> ),具体的匹配逻辑是通过AspectJ来做的.这里就不详细的说了,因为我也不懂。

在找到匹配的Advisor之后(可能有多个),他会在第一个下标为0的位置添加一个ExposeInvocationInterceptor。用来暴露当前方法调用的MethodInvocation,其实就是放在ThreadLocal里面。

还会做对所有的Advisor做排序。指定他们的执行顺序。

关于排序得说说

Advisor排序

有两次排序:

  1. 同一个@Aspect修饰的类中,不同注解之间的排序
  2. 不同@Aspect修饰的类,也就是两个类中的排序。

前提得知道Advisor是从Aspect中派生出来的,Aspect类的优先级就决定了他里面派生出来的这些Advisor的优先级。在一个Aspect内部,他有自己的优先级。

在外部,用的@Order或者Ordered接口(具体的代码)。

image-20220227174436411.png

在排序的时候指定了Comparator,开始套娃,一直到了AnnotationAwareOrderComparator

image-20220227174654589.png

在内部顺序是@Around,@Before,@After,@AfterReturning,@AfterThrowing。具体的代码在ReflectiveAspectJAdvisorFactory#Comparator<Method>属性。

image-20220227174209858.png

创建代理对象

找到满足条件的Advisor之后,通过ProxyFactory来创建代理对象。

还是在BeanPostProcessor#postProcessAfterInitialization(Object,String):void里面,找到了要应用的Advisor。就直接创建了,通过ProxyFactory来创建代理对象,具体的代码在AbstractAutoProxyCreator#createProxy(Class,String,Object[],TargetSource)

image-20220227165824071.png

到这里代理对象就创建结束了。


关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。