Spring 源码学习(八) AOP 使用和实现原理(二)

简介: 我们在业务开发中,使用得最多的是面向对象编程(OOP),因为它的代码逻辑直观,从上往下就能查看完整的执行链路。 在这个基础上延伸,出现了面向切面编程(AOP),将可以重复性的横切逻辑抽取到统一的模块中。 例如日志打印、安全监测,如果按照 OOP 的思想,在每个方法的前后都要加上重复的代码,之后要修改的话,更改的地方就会太多,导致不好维护。所以出现了 AOP 编程, AOP 所关注的方向是横向的,不同于 OOP 的纵向。 所以接下来一起来学习 AOP 是如何使用以及 Spring 容器里面的处理逻辑~

创建 AOP 代理

前面主要围绕着自动代理器 AnnotationAwareAspectJAutoProxyCreator 的注册流程来讲解,接下来看自动代理器做了什么来完成 AOP 的操作。

下面是 AnnotationAwareAspectJAutoProxyCreator 的继承体系:

1.jpg

在图片右上角,发现它实现了 BeanPostProcessor 接口,之前文章提到过,它是一个后处理器,可以在 bean 实例化前后进行扩展。查看了实现了该接口的两个方法,postProcessBeforeInitialization 没有做处理,直接返回该对象。

实际进行处理的是 postProcessAfterInitialization 方法,在 bean 实例化之后的处理,在这一步中进行里代理增强,所以来看下这个方法:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        // 组装 key
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 如果适合被代理,则需要封装指定的 bean
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // 如果已经处理过
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    // 不需增强
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    // 给定的 bean 类是否代表一个基础设施类,基础设置类不应代理 || 配置了指定 bean 不需要代理
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    // 如果存在增强方法则创建代理
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        // 增强方法不为空
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 创建代理
        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;
}

来提取一下核心流程:

  1. 获取增强方法或者增强器
    我们刚才写的 @Before@After 之类的,就是增强方法,AOP 处理时,要先找出这些增强方法。
  2. 根据获取的增强进行代理
    找到增强方法后,需要对这些增强方法进行增强代理,实际上这个 bean 已经不完全是原来的类型了,会变成代理后的类型。

获取增强方法或者增强器

入口方法在这里:

protected Object[] getAdvicesAndAdvisorsForBean(
        Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    // 寻找符合的切面
    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 从 beanFactory 中获取声明为 AspectJ 注解的类,对并这些类进行增强器的提取
    // 委派给子类实现 org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.extendAdvisors
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 寻找匹配的增强器
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

对于指定 bean 的增强方法的获取包含这两个步骤,获取所有的增强以及寻找所有增强中适用于 bean 的增强并应用。对应于 findCandidateAdvisorsfindAdvisorsThatCanApply 这两个方法。如果没找到对应的增强器,那就返回 DO_NOT_PROXY ,表示不需要进行增强。

由于逻辑太多,所以接下来贴的代码不会太多,主要来了解它的大致流程,有需要的可以跟着源码工程的注释跟踪完整的流程~:


寻找对应的增强器 findCandidateAdvisors

protected List<Advisor> findCandidateAdvisors() {
    List<Advisor> advisors = super.findCandidateAdvisors();
    if (this.aspectJAdvisorsBuilder != null) {
        // 注释 8.3 实际调用的是 org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }
    return advisors;
}

实际来看,关键是这个方法 this.aspectJAdvisorsBuilder.buildAspectJAdvisors() 这个方法看起来简单,但是实际处理的逻辑很多,代码深度也很多,所以为了避免太多代码,我罗列了主要流程,和关键的处理方法做了什么

主要流程如下:

  1. 获取所有 beanName,会将之前在 beanFactory 中注册的 bean 都提取出来。
  2. 遍历前一步骤提取出来的 bean 列表,找出打上 @AspectJ 注解的类,进行进一步处理
  3. 继续对前一步提取的 @AspectJ 注解的类进行增强器的提取
  4. 将提取结果加入缓存中

可以查询代码中的注释,从 [注释 8.3] 到 [注释 8.8 根据切点信息生成增强器] 都是这个方法的处理逻辑

※※在这个流程的最后一步中,会将识别到的切点信息(PointCut)和增强方法(Advice)进行封装,具体是由 Advisor 的实现类 InstantiationModelAwarePointcutAdvisorImpl 进行统一封装。

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
    // 简单赋值
    this.declaredPointcut = declaredPointcut;
    ...
    if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);
        this.pointcut = new PerTargetInstantiationModelPointcut(
        this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
        this.lazy = true;
    }
    else {
        // A singleton aspect.
        this.pointcut = this.declaredPointcut;
        this.lazy = false;
        // 初始化增强器
        this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
    }
}

封装体前半部分逻辑只是简单赋值。关键是这个方法 instantiateAdvice(this.declaredPointcut),在这一步中,对不同的增强(Before/After/Around)实现的逻辑是不一样的。在 ReflectiveAspectJAdvisorFactory#getAdvice 方法中区别实现了根据不同的注解类型封装不同的增强器。

public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
            MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
        ...
    // 注释 8.7 根据不同的注解类型封装不同的增强器
    switch (aspectJAnnotation.getAnnotationType()) {
        case AtPointcut:
        }
        return null;
    case AtAround:
        springAdvice = new AspectJAroundAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
        break;
    case AtBefore:
        springAdvice = new AspectJMethodBeforeAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
        break;
    case AtAfter:
        springAdvice = new AspectJAfterAdvice(
                candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
        break;
    case AtAfterReturning:
        springAdvice = new AspectJAfterReturningAdvice(
            candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
        AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
        if (StringUtils.hasText(afterReturningAnnotation.returning())) {
            springAdvice.setReturningName(afterReturningAnnotation.returning());
        }
        break;
    case AtAfterThrowing:
        springAdvice = new AspectJAfterThrowingAdvice(
            candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
        AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
        if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
            springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
        }
        break;
    default:
    }       
}

最后切点方法通过解析和封装成 Advisor,提取到的结果加入到缓存中。细心的你可能会发现除了普通的增强器外,还有另外两种增强器:同步实例化增强器和引介增强器。由于用的比较少,所以我看到源码中这两个分支处理没有深入去学习,感兴趣的同学请继续深入学习这两种增强器~


获取匹配的增强器 findAdvisorsThatCanApply

在前面流程中,已经完成了所有增强器的解析,但是对于前面解析到的增强器,并不一定都适用于当前处理的 bean,所以还需要通过一个方法来挑选出合适的增强器。

protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
    ProxyCreationContext.setCurrentProxiedBeanName(beanName);
    try {
        // 在这一步中进行过滤增强器
        return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
    }
    finally {
        ProxyCreationContext.setCurrentProxiedBeanName(null);
    }
}

可以看到,具体实现过滤操作的是工具类方法 AopUtils.findAdvisorsThatCanApply:

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
    if (candidateAdvisors.isEmpty()) {
        return candidateAdvisors;
    }
    List<Advisor> eligibleAdvisors = new ArrayList<>();
    // 遍历所有增强器
    for (Advisor candidate : candidateAdvisors) {
        // 首先处理引介增强
        if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
            eligibleAdvisors.add(candidate);
        }
    }
    boolean hasIntroductions = !eligibleAdvisors.isEmpty();
    for (Advisor candidate : candidateAdvisors) {
        // 前面处理过了,跳过
        if (candidate instanceof IntroductionAdvisor) {
            // already processed
            continue;
        }
        // 处理普通增强器类型
        if (canApply(candidate, clazz, hasIntroductions)) {
            eligibleAdvisors.add(candidate);
        }
    }
    return eligibleAdvisors;
}

具体判断逻辑在 canApply() 方法中,如果判断符合条件的,加入到 eligibleAdvisors 中,最后返回对于这个 bean 适合的增强器列表。

相关文章
|
11天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
22小时前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
20 9
|
20天前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
33 9
|
21天前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
19 1
|
26天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
26天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
26天前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
26天前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
51 2
|
26天前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
47 1
|
26天前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
18 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现