首页 > 编程语言 >Spring 源码学习笔记10——Spring AOP

Spring 源码学习笔记10——Spring AOP

时间:2022-08-24 19:48:08浏览次数:77  
标签:10 对象 Spring 代理 bean 源码 proxyFactory Advisor

Spring 源码学习笔记10——Spring AOP

参考书籍《Spring技术内幕》Spring AOP的实现章节
书有点老,但是里面一些概念还是总结比较到位
源码基于Spring-aop 5.3.22 可能和旧版本有所差异但是大体逻辑一致

一丶AOP概述

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 是一种新的模块化机制,用来描述分散在对象,类,或函数中的横切关注点,分离关注点使解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑代码中不在含义针对特定领域的代码调用,业务逻辑同特定领域问题的关系通过切面封装,维护,这样原本分散在整个应用程序中的变动可以很好地管理起来。

用人话说就是,通过切面完成特定逻辑(事务,入参出参日志等)
可以和业务逻辑(crud)抽离开,便于维护

1. Advice 通知

定义在连接点做什么,为切面增强提供植入接口。描述Spring AOP围绕方法调而注入的切面行为

2.Pointcut切入点

切点决定Advice通知应该作用在哪个连接点,也就是通过Poincut来定义需要增强的方法集合,这些集合可以按照一定规则来完成,这种情况下,Pointcut意味着标识方法(比如事务切面定义了事务注解方法上生效)切入点是一些列织入逻辑代码的连接点集合

3.Advisor通知器

整合Advice 和 Pointcut,定义应该使用哪个通知器并在哪个关注点使用它。

二丶aop 重要接口和编程体验

我们先抛弃Spring框架,利用Spring aop中存在的工具实现aop增强。

1.基于Advice

image-20220824154409542

Advice接口的实现有AfterAdvice后置通知Beforeadvice前置通知MethodInterceptor方法拦截器可以看做是环绕通知。

/**
 * 服务类
 */
public static class Service{
    public void doSomething(){
        System.out.println("service doSomething");
    }
}

/***
 * 自定义的advice 环绕通知
 */
public static class MyAdvice implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("my advice before");
        //service 方法执行
        Object res = invocation.proceed();
        System.out.println("my advice after");
        return res;
    }
}

public static void main(String[] args) {
    //代理工程 用于生成代理对象
    ProxyFactory proxyFactory = new ProxyFactory();
    //目标对象 
    Service service = new Service();
    
    //设置需要代理的对象
    proxyFactory.setTarget(service);
    proxyFactory.addAdvice(new MyAdvice());
    
    //生成代理对象
    Service proxyService = (Service) proxyFactory.getProxy();
    //代理对象执行
    proxyService.doSomething();
}

上述代码执行结果

image-20220824155430057

基于Advice,ProxyFactory成功实现了Aop代理增强

2.基于Advisor

image-20220824162331408

public static void advisorBased(){
    //代理工程 用于生成代理对象
    ProxyFactory proxyFactory = new ProxyFactory();
    //目标对象
    Service service = new Service();
    
    //advisor
    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
    //根据名称匹配方法的pointcut
    NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
    //指定只有doSomething 才增强
    pointcut.setMappedName("doSomething");
    advisor.setPointcut(pointcut);
    advisor.setAdvice(new MyAdvice());
    
    
    //设置需要代理的对象
    proxyFactory.setTarget(service);
    proxyFactory.addAdvisor(advisor);
    //生成代理对象
    Service proxyService = (Service) proxyFactory.getProxy();
    //代理对象执行
    proxyService.doSomething();
    System.out.println();
    proxyService.sayHello();
}

image-20220824161712037

最后我们发现只有名称匹配的方法才生效。

Advisor接口具备方法Advice getAdvice()来获取通知。PointcutAdvisor实现了Advisor并且新增方法Pointcut getPointcut()来获取切入点的定义。Pointcut接口定义了两个方法ClassFilter getClassFilter(),MethodMatcher getMethodMatcher()分别是对类和方法的筛选,来决定Advise是不是应该作用于当前类。

三丶ProxyFactory 和 ProxyFactoryBean

1.ProxyFactory

1.1类图

image-20220824163045792

TargetSource 用于获取 AOP 调用的当前“目标”

image-20220824163652106

getTargetClass可以获取被代理对象的类型,getTarget可以获取被代理对象,HotSwappableTargetSource中的swap方法可以替换掉代理对象,Spring aop常用的是SingletonTargetSource它持有了原始的被代理对象。

1.2 proxyFactory是如何创建代理对象的。

public Object getProxy() {
   return createAopProxy().getProxy();
}

image-20220824164249060

1.2.1 DefaultAopProxyFactory # createAopProxy

image-20220824164734093

这里生成的AopProxy 才是负责生成代理对象的,其中spring内置了两种策略——JDK动态代理和CGLIB动态代理。

只有设置了需要代理目标类,或者说没有指定代理的接口,且代理目标类不是接口,不是lambda,不是已经被JDK动态代理后的类,那么才会使用CGLIB进行动态代理。

1.2.2 AopPorxy

image-20220824165158029

其中JdkDynamicAopProxy,还实现了InvocationHandler

  • Jdk动态代理

  • 生成代理对象

    Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this),这里的this便是自己。

  • invoke

首先是对`equals`,`hashCode`的处理,目标对象声明了让目标对象执行,反之调用`JdkDynamicAopProxy`对应的方法

其次是如果配置中设置了暴露代理对象,那么将其放入到`AopContext`中的`ThreadLocal`中

```java
if (this.advised.exposeProxy) {
   // Make invocation available if necessary.
   oldProxy = AopContext.setCurrentProxy(proxy);
   setProxyContext = true;
}
```

然后获取当前对象的拦截器链,如果拦截器链为空 那么直接反射调用目标对象的方法。如果存在拦截器链那么new 一个`ReflectiveMethodInvocation`利用反射依次执行。Spring只支持方法拦截器`MethodInterceptor`进行代理增强,对于Advise都会适配成`MethodInterceptor`,Spring采用适配器模式,具体的适配器如下

![image-20220824172451573](/i/l/?n=22&i=blog/2605549/202208/2605549-20220824172515386-796996823.png)

```java
public interface AdvisorAdapter {
   //支持什么Advise被适配
   boolean supportsAdvice(Advice advice);

   //适配
   MethodInterceptor getInterceptor(Advisor advisor);
}
```

自然是Spring遍历每一个Advise责任链模式依次找到`AdvisorAdapter`然后调用适配方法得到一个`MethodInterceptor`,下面是适配成的`MethodInterceptor`。

![image-20220824172904504](/i/l/?n=22&i=blog/2605549/202208/2605549-20220824172910944-1903834494.png)
  • CGLIB动态代理

    • 设置CallBack

      首先new 一个Enhancer设置父类为被代理对象的类型,这里会把讲Aop的逻辑转变为一个DynamicAdvisedInterceptor,equals和hashCode方法也有对应的callBack

      image-20220824174533939

      注意这里的MethodInterceptororg.springframework.cglib.proxy.MethodInterceptor,其中的intercept 方法的逻辑和JDK动态代理的invoke类似,都是链式调用。

2.ProxyFactoryBean

image-20220824175124978

ProxyFactoryBean创建代理对象的逻辑和ProxyFactory类似,但是ProxyFactoryBean是一个FactoryBean,我们可以利用这一点在bean初始化的时候生成一个代理对象

image-20220824175752977

image-20220824180011325

创建原型对象类似,但是不会判断之前是否创建过,直接无脑创建即可

四丶Spring AOP 和IOC是如何结合起来的

通常我们在使用Spring Aop的时候会在启动类上加一个@EnableAspectJAutoProxy注解,这个注解@Import(AspectJAutoProxyRegistrar.class)导入了AspectJAutoProxyRegistrar,这个类实现了ImportBeanDefinitionRegistrar,Spring容器会调用其registerBeanDefinitions方法为我们注入BeanDefinition,后续会实例化一个AnnotationAwareAspectJAutoProxyCreator类型的bean,它是Spring IOC和AOP结合的关键

1.AnnotationAwareAspectJAutoProxyCreator类结构图

image-20220824180746690

这其中最为关键的必然是AnnotationAwareAspectJAutoProxyCreator是一个BeanPostProcessor,从而在Spring 回调postProcessAfterInitialization对bean进行代理的增强,并且它实现了SmartInstantiationAwareBeanPostProcessor Spring容器创建bean的时候如果出现了循环依赖那么会调用到getEarlyBeanReference,在这个方法里面同样也会进行aop的增强

  • AbstractAutoProxyCreator 实现了SmartInstantiationAwareBeanPostProcessor是一个bean后置处理器,使用 AOP 代理包装每个符合条件的 bean,在调用 bean 本身之前委托给指定的拦截器,AOP代理发生的地方。

  • AbstractAdvisorAutoProxyCreator

    为了每一个Bean找到合适的Advisor并且进行,如果Advisor标注了@Order或者说实现了Ordered接口那么会进行排序。

  • AspectJAwareAdvisorAutoProxyCreator

    AbstractAdvisorAutoProxyCreator子类,对一个切面中的多个Advisor进行优先级排序

  • AnnotationAwareAspectJAutoProxyCreator

    AspectJAwareAdvisorAutoProxyCreator的子类,会将容器中标注了@AspectJ注解的类解析成Advisor(整合Advice 和 Pointcut,定义应该使用哪个通知器并在哪个关注点使用它)

2.AbstractAutoProxyCreator是如何进行Aop增强的

image-20220824181252842

image-20220824181353002

进行AOP增强的地方其实还有postProcessBeforeInstantiation如果我们自己配置了TargetSourceCreator并且可以为当前bean生成,那么才可能发生aop,这里一般不会进行任何操作。

2.1 wrapIfNecessary

image-20220824182002813

其中shouldSkipAspectJAwareAdvisorAutoProxyCreator重写,如果AdvisorAspectJPointcutAdvisor并且切面名称和bean名称相同那么会跳过,这应该是我们标注@Aspect的时候需要保证这个类会被Spring加入到容器,所有需要加@Componet所以会过滤掉

2.1.1 如何找到所有合适的advice 和advisor

image-20220821162528516

  • findCandidateAdvisors方法会找到容器中所以的Advisor类型的bean,AnnotationAwareAspectJAutoProxyCreator进行了重写,它还会把所以标注了@Aspect注解的bean中的增强逻辑封装成Advisor

  • findAdvisorsThatCanApply这个方法内部逻辑基本上就是调用PointcutAdvisor获取类过滤器,方法匹配器进行匹配。

  • sortAdvisors 这里默认是通过@Order注解,或者Ordered接口进行排序,但是AspectJAwareAdvisorAutoProxyCreator进行了重写,因为它需要对同一个标注@Aspect切面里面的前置后置等进行排序

2.1.2 创建代理对象

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);
   //这里的ProxyTargetClass 来自上面的copyFrom 取决于EnableAspectJAutoProxy注解的proxyTargetClass
    //proxyTargetClass 表示是否使用基于CGLIB子类的代理
   if (!proxyFactory.isProxyTargetClass()) {
       //shouldProxyTargetClass 方法就是去BeanFactory中看当前bean的BeanDefinition中是否存在AutoProxy.PRESERVE_TARGET_CLASS_ATTRIBUTE=trued的attribute,当我们手动注入bean的时候可以使用这个强制让当前bean使用CGLIB增强
      if (shouldProxyTargetClass(beanClass, beanName)) {
         proxyFactory.setProxyTargetClass(true);
      }
      else {
         //获取当前类中非Spring回调(InitializingBean,DisposableBean,Aware)类型的接口,且如果接口的方法大于0,那么会把接口类型加入到proxyFactory中,否则设置ProxyTargetClass(没有接口那么没办法使用JDK动态代理)
         evaluateProxyInterfaces(beanClass, proxyFactory);
      }
   }
   
   //主要是把上面找到的advise 适配成Advisor。调用的是advisorAdapterRegistry的wrap方法
   Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);    
   proxyFactory.addAdvisors(advisors);
   //这里的targetSource是SingletonTargetSource
   proxyFactory.setTargetSource(targetSource);
   //留给子类扩展的方法
   customizeProxyFactory(proxyFactory);
    
   proxyFactory.setFrozen(this.freezeProxy);
   if (advisorsPreFiltered()) {
      proxyFactory.setPreFiltered(true);
   }
	//生成代理对象
   return proxyFactory.getProxy(getProxyClassLoader());
}

这里其实和我们上面的Aop编程体验中基于Advisor类似,最后都是AopProxy创建代理对象

2.2 AnnotationAwareAspectJAutoProxyCreator

上面我们讲了其父类AbstractAutoProxyCreator的大体逻辑,AnnotationAwareAspectJAutoProxyCreator会将@Aspect注解类解析成Advisor,下面我们重点看下AnnotationAwareAspectJAutoProxyCreator是怎么将@Aspect注解类解析成Advisor

image-20220824190521871

这里依赖了BeanFactoryAspectJAdvisorsBuilder,它会遍历所有bean,并调用isAspect方法

image-20220824190659633

然后调用ReflectiveAspectJAdvisorFactorygetAdvisors方法将其适配成多个Advisor,会遍历每一个没有标注@Pointcut的方法,然后获取@Around, @Before, @After, @AfterReturning, @AfterThrowing(如果没有那么直接返回)然后获取value中的内容包装成AspectJExpressionPointcut(AspectJ表达式pointcut),然后包装成InstantiationModelAwarePointcutAdvisorImpl在这个类中会把对应注解的方法封装成对应的AbstractAspectJAdvice的子类

image-20220824191958027

调用对应方法依旧采用反射,其子类在合适的实际进行调用。

标签:10,对象,Spring,代理,bean,源码,proxyFactory,Advisor
From: https://www.cnblogs.com/cuzzz/p/16621320.html

相关文章

  • LNMP架构的源码编译
    LNMP架构的源码编译一、LNMP架构的编译安装1.安装nginx服务(1)关闭防火墙[root@localhost~]#systemctlstopfirewalld[root@localhost~]#systemctldisablefirew......
  • Codeforces Round #710 (Div. 3) D. Epic Transformation(优先队列)
    https://codeforces.com/contest/1506/problem/D鉴定完毕,这题卡映射数量到优先队列的那一步操作给你一个由整数组成的长度为n的数组a。您可以对阵列应用由几个步骤组成......
  • SpringBoot Excel导入导出
    一、引入pom.xml依赖<!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><......
  • 多位时间戳转换(10、11、13位时间戳都适用)
    10、11、13位时间戳都适用<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><met......
  • SpringBoot使用RabbitMq实现队列和延时队列
    闲来无事看了看RabbitMq的队列,总结了一些队列的实现方法,当然,免不了各种看别人的博客哈哈哈其中延时队列有两种方式,一种是使用TTl+死信队列实现,一种是直接用RabbitMq的官方......
  • SpringMVC、MVC、JavaBean、表述层、三层架构
    来源:BV1Ry4y1574R?p=3、BV1Ry4y1574R?p=4SpringMVC是Spring的MVC模块,也就是来实现Web功能的模块。Spring里面包含有很多的模块,比如说SpringFramework是它的......
  • win10、win11环境下查看IIS里各项目资源占用情况
    参照链接:【如何设置IIS程序池的回收时间,才能最大程度的减少对用户的影响?】-走看看(zoukankan.com)概念:简单理解IIS应用程序池应用程序池可以看成是计算机分配给Web......
  • 新字符设备驱动原理和框架源码分析
    一、分配和释放设备号动态申请设备号:/*dev:设备号--dev_tdevid;count:是要申请的数量,一般都是一个;name:是设备名字*/intalloc_chrdev_region(dev_t*dev,uns......
  • 【转载】Python(cx_oracle)的DPI-1047错误
    转自:https://blog.csdn.net/weixin_45158749/article/details/124800132 Python(cx_oracle)的DPI-1047错误步步FAN已于2022-05-1615:19:11修改981收藏文章标签:......
  • CBV源码剖析
    你自己不要修改源码除了bug很难找突破口在urls.pyurl(r'^login/',views.MyLogin.as_view())url(r'^login/',views.view)FBV一模一样CBV与FBV在路由匹配上本质是一样......