h

hitechr

V1

2022/08/21阅读:26主题:极客黑

AOP的原理:准备工作

相关概念

连接点Joinpoint

程序执行到某个特定的位置点,一个类或一个程序代码天然就拥有一些具有边界性质的特殊点,比如一个方法的开始执行前、执行后、异常后、返回前等,这些点称为连接点。

切点Pointcut

如果说连接点是一条条的数据记录,那切点就是查询这些记录的筛选条件,一个切点可以筛选出多个连接点。Spring通过定义一个表达式去筛选满足条件的连接点。

增强Advice

根据切点找出一满足条件的连接点后,就得加入我们自己定义的一些业务代码,这些业务代码就是Advice。Spring中提供的增强接口都是带一些方位名(连接点):BeforeAdviceAfterAdvice

切点Aspect

在对方法进行扩展时,我们需要知道在方法的哪些边界点(连接点)、边界点的筛选条件(切点)、找到边界点后要做什么(增强),而连接点是代码本身就具有的边界性质,所以我们只需要自己定义好切点和增强就行,拥有切点和增强的类,我们称为切面。

EnableAspectJAutoProxy

开启AOP的功能,这上注解上被@Import注解,会导入AspectJAutoProxyRegistrar

知识回顾

在用 AnnotationConfigurationContext这类进行启动spring时,会创建一个用于读取BeanDefinition的工具类AnnotationBeanDefinitionReader,然后再利用工具类AnnotationConfigUtils往spring容器中注册注解相关的BeanDefinitionConfigurationClassPostProcessorAutowiredAnnotationBeanPostProcessorCommonAnnotationPostProcessor等。

ConfigurationClassPostProcessor:是一个BeanFactoryPostProcessor的实现类,主要解析和注册BeanDefinition的,如:@Component@PropertySource@ComponentScan@Import @ImportSource@Bean,其中AOP就是利用@Import导入代理相关的类。

准备代理类

  1. 首先在启动类上添加@EnableAspectJAutoProxy个开启AOP的代理功能。
  2. 在spring的启动过程中通过refresh()方法去执行invokeBeanFactoryPostProcessors(),这个方法去实例并执行容器中已经注册好的BFPP。
  3. 当执行到ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry时,去解析每个类个的注解元数据信息。
  4. 当解析到@EnableAspectJAutoProxy时,发现这个类被@Import(AspectJAutoProxyRegistrar.class)修饰,于是再去导入AspectJAutoProxyRegistrar这个类到ConfigurationClass中,因为AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,所以后面就去调用这个接口的registerBeanDefinitions方法来注册代理类的BeanDefinition
  5. 当调用registerBeanDefinitions方法时,通过AopConfigUtils往容器中注册AnnotationAwareAspectJAutoProxyCreator的定义信息,由于这个接口是BeanPostProssor的子实现类,所有在后面的注册并实例化BeanPostProssor的方法中会被调用到。

至此@Aspect注解还没被解析到,只是同时被@Compontent@Aspect同时修饰的BD注册到容器中,目前容器只知道它是一个普通的BD,还不知道是个切面类, @Aspect的解析工作在的AnnotationAwareAspectJAutoProxyCreator中完成。

Aspect的解析

AnnotationAwareAspectJAutoProxyCreator的继承关系

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
 //实例化前执行
 @Nullable
 default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException{
  return null;}

 //* 实例化后执行
 default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
  return true;}
 //设置属性
 @Nullable
 default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
   throws BeansException 
{
      return null;}
}

要创建一个代理对象的,前提是创建代理对象的构造器类(Creator)得先有。

  1. 当在createBean方法创建一个对象前,首先会调用resolveBeforeInstantiation方法,会去尝试先获取一个对象,如果能拿到对象就直接返回,否则就去执行后面的doCreateBean。 感觉这个方法就专门为代理对象构造器类准备的,只有AnnotationAwareAspectJAutoProxyCreator有具体的实现,其它几个返回的都是空。
  2. 获取所有InstantiationAwareBeanPostProcessors的实现类,然后去执行postProcessBeforeInstantiation方法。

在spring中对外提供一个Advisor的包装类,一个Advisor都包装一个Advice方法,而Advice都在@Aspect修饰的类中,所以就形成了这样的一个一对多的关系。aspect对应多个Advisor,每个Advisor对应一个Advice,因此在先找到被@Aspect修饰的类。

  1. spring首先获取所有的beanNames,逐个进行遍历,找到被@Aspect修饰的类
  2. 然后在获取这个类里面的所有方法,每个满足条件的方法都会生成一个AspectJExpressionPintcut表达式类,然后再获取获取对应的增强方法,把其包装为一个Advisor进行返回。
  3. 并把找到的Advisior集合与切面名保存到一个集合中方便下次使用时直接获取

发现在整个解析AOP注解的过程中没有把相应的注解修饰的类或方法生成BeanDefinition,而是直接获取,保存到缓存中,没有生成BD信息这点与xml的方式不太一样。

每个连接点对应的Advice实现类

连接点 advice
around AspectJAroundAdvice
before AspectJMethodBeforeAdvice
after AspectJAfterAdvice
AfterReturning AspectJAfterReturningAdvice
AtAfterThrowing AspectJAfterThrowingAdvice

总结

  1. spring启动时通过ConfigurationClassPostProssor解析@Import标签时把@EnableAspectJAutoProxy上的AspectJAutoProxyRegistar这个BPP的实现类注册到容器中
  2. 在创建单例对象时的resolveBeforeInstantiation方法去解析切面相关的注解
  3. 通过遍历所有的BeanNames,找出被@Aspect修饰的类,进而去解析出被@Before,@After等修饰的增强方法,与表达式对象AspectJExpressionPointcut封装为一个Advice对象,然后把Advice包装为一个Advisor增强器类,把切面与Advisor集合保存到advisorsCache的一个缓存中,方便下次使用时直接获取。

分类:

后端

标签:

后端

作者介绍

h
hitechr
V1