菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
469
0

Spring AOP原理学习

原创
05/13 14:22
阅读数 14238

本文主要介绍Spring中AOP的原理,至于AOP是什么以及如何使用AOP,请参考此处

环境:SpringBoot 2.4.2

有这样的代码

public class MathCalculator {

    public int div(int i, int j) {
        System.out.println("div..." + i + "/" + j);
        return i / j;
    }
}
@Aspect
public class LogAspects {

    @Pointcut("execution(public int com.hjc.demo.aop.MathCalculator.*(..))")
    public void pointCut() {}

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        System.out.println("@Before: " + joinPoint.getSignature().getName()
                + "running... args are {" + Arrays.asList(args) + "}");
    }

    @After("pointCut()")
    public void logEnd(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        System.out.println("@After: " + joinPoint.getSignature().getName()
                + "div ending... args are {" + Arrays.asList(args) + "}");
    }

    @AfterReturning(value="pointCut()", returning="result")
    public void logReturn(JoinPoint joinPoint, Object result) {
        System.out.println("@AfterReturning: " + joinPoint.getSignature().getName()
                + "div return... return value is {" + result + "}");
    }

    @AfterThrowing(value="pointCut()", throwing="exception")
    public void logException(JoinPoint joinPoint, Exception exception) {
        System.out.println("@AfterThrowing: " + joinPoint.getSignature().getName()
                + "div has exception... details: {" + exception + "}");
    }
}
@EnableAspectJAutoProxy
@Configuration
public class AopConfig {

    @Bean
    public MathCalculator mathCalculator() {
        return new MathCalculator();
    }

    @Bean
    public LogAspects logAspects() {
        return new LogAspects();
    }
}

进行单元测试,就能显示使用了AOP的结果

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    private MathCalculator mathCalculator;

    @Test
    public void testAop() {
        mathCalculator.div(1, 1);
    }
}

@EnableAspectJAutoProxy

由上面的代码可见,AOP的功能要起作用,@EnableAspectJAutoProxy注解很关键,不加上这个注解就没有AOP的功能,我们从这个注解出发,研究AOP原理

我们进入这个注解的源码

可以看到@Import({AspectJAutoProxyRegistrar.class})这一行会给容器中导入AspectJAutoProxyRegistrar这个类,利用这个类可以自定义给容器中注册bean

在这个类的方法上加上断点,开启debug

进入方法的第一行

可以看到,AspectJAutoProxyRegistrar这个类给容器中注册了AnnotationAwareAspectJAutoProxyCreator组件

AnnotationAwareAspectJAutoProxyCreator

接下来,我们主要研究AnnotationAwareAspectJAutoProxyCreator这个类的作用。查看这个类的源代码,可以看到这个类的继承树

这个类最终是实现了SmartInstantiationAwareBeanPostProcessor这个后置处理器和BeanFactoryAware,后置处理器的工作就是在bean初始化前后做一些额外的事情,所以AnnotationAwareAspectJAutoProxyCreator这个类既是一个后置处理器,又是一个Aware接口的实现类

和后置处理器相关

在IOC容器启动之后,创建和注册了许多BeanPostProcessor后置处理器,在bean的初始化过程中执行了后置处理器的“before”和“after”方法。在这其中,InstantiationAwareBeanPostProcessor作为一个后置处理器也创建成功。创建完后,Spring会把BeanPostProcessor注册到BeanFactory中,((AbstractBeanFactory)beanFactory).addBeanPostProcessors(postProcessors);

以上便是创建和注册AnnotationAwareAspectJAutoProxyCreator的大致过程

此处首先区分BeanPostProcessorInstantiationAwareBeanPostProcessor,前者是后者的父类,前者是在Bean对象创建完成初始化前后调用的,而后者是在创建Bean实例之前先尝试用后置处理器返回对象的,这两者的调用时机不同。AnnotationAwareAspectJAutoProxyCreator属于后者,也就是说AnnotationAwareAspectJAutoProxyCreator在所有Bean实例创建之前有一个拦截,此时会调用postProcessBeforeInstantiation()方法,此方法位于其父类AbstractAutoProxyCreator中,重写了InstantiationAwareBeanPostProcessor这个接口对应的方法

postProcessBeforeInstantiation

我们开启debug,观察容器中MathCalculator组件创建之前调用postProcessBeforeInstantiation()方法的时刻

可以看到

  1. 首先判断当前Bean是否在advisedBeans中,advisedBeans保存了所有需要增强的Bean

  2. 判断当前Bean是否是基础类型isInfrastructureClass()或者是否是切面isAspect(),所谓基础类型,就是AdvicePointcutAdvisorAopInfrastructureBean
    如果是,则放入advisedBeans中,这里主要是用来处理切面类

  3. 判断是否要跳过,不处理这个Bean

postProcessAfterInitialization

在执行完postProcessBeforeInstantiation()之后,Spring会创建对象

创建完对象后,会执行postProcessAfterInitialization()方法

这里的关键是这一行代码this.wrapIfNecessary(bean, beanName, cacheKey);,我们进入这个方法

可以看到

  1. 首先获取当前Bean匹配的所有增强器,也就是通知方法
    具体方法分为三步,首先找到候选的所有增强器,也就是所有通知方法,接着就从中获取能在当前Bean使用的增强器,最后对找到的增强器进行排序

  2. 保存当前Bean在advisedBeans中

  3. 如果当前Bean需要增强,就创建Bean的代理对象
    获取所有增强器(通知方法),并保存到proxyFactory
    根据被拦截的Bean是否实现了接口,Spring自动决定使用JdkDynamicAopProxy(jdk动态代理)还是ObjenesisCglibAopProxy(cglib动态代理)创建代理对象

  4. 给容器中返回当前组件用cglib增强了的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程

目标方法执行流程

在创建完代理对象之后,我们来到目标方法的执行处

可以看到,此时的MathCalculator已经是代理对象,这个代理对象里面保存了详细信息,比如增强器,目标对象等

我们进入目标方法,来到CglibAopProxyintercept()方法处,这个方法便是拦截目标方法执行

  1. 首先根据ProxyFactory对象获取将要执行的目标方法拦截器链,所谓拦截器链,就是每个通知方法被包装为方法拦截器,之后会利用MethodInterceptor机制
    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
    方法内部首先创建一个List<Object> interceptorList = new ArrayList<>(advisors.length);
    遍历所有增强器,将其转为Interceptor -->> registry.getInterceptors(advisor)
    如果增强器已经是MethodInterceptor,直接加入到集合中;如果不是,则使用AdvisorAdapter将增强器转为MethodInterceptor,转换完成返回MethodInterceptor数组

  2. 如果没有拦截器链,直接执行目标方法

  3. 如果有拦截器链,把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个CglibMethodInvocation,并调用proceed()方法,并处理得到的返回值
    proceed方法中会进行拦截器链的触发过程,拦截器链的触发过程遵循责任链模式

总结

  1. 使用@EnableAspectJAutoProxy注解开启Spring的AOP功能

  2. @EnableAspectJAutoProxy注解会给容器中注册AnnotationAwareAspectJAutoProxyCreator组件

  3. AnnotationAwareAspectJAutoProxyCreator组件是一个后置处理器

  4. 在容器的创建流程中会有注册后置处理器的过程,之后便是初始化Bean的过程,在初始化Bean的过程中,该后置处理器会创建业务逻辑组件和切面组件,在组件创建完后,后置处理器会判断该组件是否需要增强,增强的话就将切面的通知方法包装为增强器,并给目标对象创建一个代理对象(使用jdk或者cglib)

  5. 执行目标方法的过程就是代理对象执行目标方法的过程
    使用CglibAopProxyintercept()方法进行拦截的话,会得到目标方法的拦截器链(将增强器包装为MethodInterceptor)
    利用拦截器的链式机制,依次进入每一个拦截器进行执行
    最终效果:
    正常执行:前置通知 -> 目标方法 -> 后置通知 -> 返回通知
    出现异常:前置通知 -> 目标方法 -> 后置通知 -> 异常通知

发表评论

0/200
469 点赞
0 评论
收藏