SpringBoot源码解读与原理分析(十六)SpringBoot的AOP支持

本文详细介绍了SpringBoot中AOP(面向切面编程)的支持,包括AOP术语、Spring整合AOP的简单示例,以及如何通过@EnableAspectJAutoProxy启用AOP。涵盖了前置、后置、返回、异常和环绕通知类型,并剖析了相关配置和注册过程。
摘要由CSDN通过智能技术生成

5 SpringBoot的AOP支持

面向切面编程(AOP)是SpringFramework的核心特性之一,其核心是切面(Aspect)。AOP可以在不修改功能代码本身的前提下,使用运行时动态代理技术对已有代码逻辑进行增强。

5.1 SpringFramework的AOP

5.1.1 AOP术语

AOP的基本术语包括:

  • Target:目标对象,即被代理的对象。
  • Proxy:代理对象,即经过代理后生成的对象(如Proxy.newProxyInstance返回的结果)。
  • JoinPoint:连接点,即目标对象的所属类中定义的所有方法。
  • PointCut:切入点,即那些被拦截/被增强的连接点。连接点和切入点是包含关系,切入点可以是0个或者多个(甚至全部)连接点的组合。换句话说,切入点一定是连接点,连接点不一定是切入点。
  • Advice:通知,即增强的逻辑,也就是增强的代码。
  • Aspect:切面,即切入点与通知组合之后形成的产物。
  • Weaving:织入,即将Advice(通知)应用到Target(目标对象),进而生成Proxy(代理对象)的过程。
  • Introduction:引介,对标Advice(通知)的概念,通知是针对切入点提供增强的逻辑,而引介是针对Class(类),它可以在不修改原有类的代码的前提下,在运行时为原始类动态添加新的属性/方法。

两个公式:

  • Proxy(代理对象) = Target(目标对象) + Advice(通知),这个公式中的+号就是Weaving(织入)
  • Aspect(切面) = PointCut(切入点) + Advice(通知) + Introduction(引介)

5.1.2 通知类型

SpringFramework支持5种基于AspectJ的通知类型:

  • Before前置通知:目标对象的方法调用之前触发。
  • After后置通知:目标对象的方法调用之后触发。
  • AfterReturning返回通知:目标对象的方法调用成功,在返回结果值之后触发。
  • AfterThrowing异常通知:目标对象的方法在运行时抛出/触发异常后触发。
  • Around环绕通知:编程式控制目标对象方法的调用。

注意2点:

  • AfterReturning返回通知和AfterThrowing异常通知是互斥的。如果方法成功调用无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接获取目标对象以及要执行的方法,因此环绕通知可以任意在目标对象的方法调用前后扩展逻辑,甚至不调用目标对象的方法。

5.2 SpringBoot整合AOP的简单例子

(1)导入依赖:spring-boot-starter-aop

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

(2)编写组件类

@Service
public class DemoService {

    public void test() {
        System.out.println("DemoService.test run ......");
    }

}

(3)编写切面类:使用@Aspect、@Before注解

@Aspect
@Component
public class DemoServiceAspect {

    @Before("execution(public * com.xiaowd.springboot.aop.test01.*.*(..))")
    public void beforeTest() {
        System.out.println("DemoServiceAspect.beforeTest run ......");
    }

}

(4)编写主启动类:使用@EnableAspectJAutoProxy注解,即可开启基于注解驱动的AOP

@SpringBootApplication
@EnableAspectJAutoProxy
public class Test01App {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Test01App.class, args);
        context.getBean(DemoService.class).test();
    }

}

(5)测试

DemoServiceAspect.beforeTest run ......
DemoService.test run ......

切面类的beforeTest方法在组件类的test方法之前执行了,说明SpringBoot整合AOP场景顺利完成。

5.3 AOP的开关:@EnableAspectJAutoProxy

其源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;
    boolean exposeProxy() default false;
}

@EnableAspectJAutoProxy注解包含两个属性:

  • proxyTargetClass:是否直接代理目标类,即强制使用Cglib代理。
  • exposeProxy:是否暴露当前线程的AOP上下文。开启后,通过AopContext可以获取到当前的代理对象本身。

除了注解属性,更重要的是使用@Import注解导入了一个AspectJAutoProxyRegistrar。

5.3.1 AspectJAutoProxyRegistrar

从类名看,这是一个基于AspectJ的自动代理注册器,它的javadoc如下:

Registers an AnnotationAwareAspectJAutoProxyCreatoragainst the current BeanDefinitionRegistryas appropriate based on a given EnableAspectJAutoProxyannotation.

基于给定的@EnableAspectJAutoProxy注解,根据当前BeanDefinitionRegistry在适当的位置注册AnnotationAwareAspectJAutoProxyCreator。

5.3.1.1 注册核心代理组件的逻辑

AspectJAutoProxyRegistrar注册核心代理组件的源码如下:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(
        AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 此处有注册新的BeanDefinition的动作
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
    
        AnnotationAttributes enableAspectJAutoProxy =
            AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        // 给AnnotationAwareAspectJAutoProxyCreator设置属性值
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
    
}

由源码可知,AspectJAutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,因此具备编程式注册新的BeanDefinition的能力。AspectJAutoProxyRegistrar注册核心代理组件的关键是第一行代码:AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

5.3.1.2 注册AOP代理创建器

编程式注册AOP代理创建器的源码如下:

AopConfigUtils.java

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
    return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
}

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
    BeanDefinitionRegistry registry, @Nullable Object source) {
    // 这里把AnnotationAwareAspectJAutoProxyCreator的字节码类型传入
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

public static final String AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator";

@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(
Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
    
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }
    // 手动创建RootBeanDefinition并传入AnnotationAwareAspectJAutoProxyCreator类型
    // 设置了最高级别的优先级等其他属性和配置项
    // 最后注册到BeanDefinitionRegistry中
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

从源码可知,最终要注册的AOP代理创建器的落地实现是AnnotationAwareAspectJAutoProxyCreator,并通过手动创建RootBeanDefinition注册到BeanDefinitionRegistry中。

5.3.2 AnnotationAwareAspectJAutoProxyCreator

AspectJAwareAdvisorAutoProxyCreatorsubclass that processes all AspectJ annotation aspects in the current application context, as well as Spring Advisors.
Any AspectJ annotated classes will automatically be recognized, and their advice applied if Spring AOP’s proxy-based model is capable of applying it. This covers method execution joinpoints.
If the <aop:include> element is used, only @AspectJ beans with names matched by an include pattern will be considered as defining aspects to use for Spring auto-proxying.
Processing of Spring Advisors follows the rules established in org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.

AnnotationAwareAspectJAutoProxyCreator是AspectJAwareAdvisorAutoProxyCreator的子类,用于处理当前应用上下文中的所有基于AspectJ注解的切面,以及Spring原生的Advisors。

  • 如果SpringAOP基于代理的模型能够应用任何被@AspectJ注解标注的类,那么它们的增强方法将被自动识别。这涵盖了方法执行的切入点表达式。
  • 如果使用<aop:include>元素,则只有名称与包含模式匹配的被@AspectJ标注的Bean被视为定义要用于Spring自动代理的切面。
  • Spring中内置的Advisors的处理遵循AbstractAdvisorAutoProxyCreator中建立的规则。

该类的javadoc的重点是:AnnotationAwareAspectJAutoProxyCreator不仅支持AspectJ风格的切面声明,还支持SpringFramework原生的AOP编程。只是原生的AOP编程已经基本不再使用。

5.3.2.1 继承结构

借助IDEA可以查看AnnotationAwareAspectJAutoProxyCreator的继承结构:
AnnotationAwareAspectJAutoProxyCreator的继承结构
其中有几个重要的根接口值得关注:

  • BeanPostProcessor:用于在postProcessAfterInitialization方法中生成代理对象。
  • InstantiationAwareBeanPostProcessor:拦截Bean的正常doCreateBean创建流程。
  • SmartInstantiationAwareBeanPostProcessor:提前预测Bean的类型、暴露Bean的引用。
  • AopInfrastructureBean:实现了该接口的Bean永远不会被代理,以防止反复被代理导致逻辑死循环。
5.3.2.2 初始化时机

由上面的继承结构可知,AnnotationAwareAspectJAutoProxyCreator本身是一个后置处理器。

SpringBoot源码解读与原理分析(十三)IOC容器的启动流程 中提到,后置处理器的初始化时机是在AbstractApplicationContext中的refresh方法的第6步,即执行registerBeanPostProcessors方法。

AbstractApplicationContext.java

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // ...

            // Register bean processors that intercept bean creation.
            // 6.注册Bean的后置处理器
            registerBeanPostProcessors(beanFactory);

            // ...

        }
    }
}

registerBeanPostProcessors方法会按照既定的排序规则初始化所有的BeanPostProcessor。

本文【5.3.1.2 注册AOP代理创建器】贴出的源码指出,AnnotationAwareAspectJAutoProxyCreator实现了Ordered接口,并且声明了最高优先级,这就是意味着它会比其他的BeanPostProcessor更先创建,从而也会感觉这些普通BeanPostProcessor的初始化。

5.3.2.3 作用时机

与其他BeanPostProcessor相似,AnnotationAwareAspectJAutoProxyCreator的作用时机通常是在bean对象的初始化阶段介入处理,而代理对象的创建时机在初始化逻辑之后执行(即postProcessAfterInitialization),这是由于SpringFramework尽可能保证Bean的完整性。

AbstractAutoProxyCreator.java
/**
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 核心动作:构造代理
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

以上源码的核心动作是wrapIfNecessary方法,方法名的意思是:如果有必要,将当前对象包装成代理对象。

AbstractAutoProxyCreator.java

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对象
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    
    // Create proxy if we have advice.
    // 根据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;
}

梳理以上源码,可以大致分为3步:

  1. 判断是否是一个不会被增强的bean对象;
  2. 根据bean对象去匹配增强器;
  3. 如果有增强器,则创建bean对象的代理对象。

5.4 小结

第5章到此就梳理完毕了,本章的主题是:SpringBoot的AOP支持。回顾一下本章的梳理的内容:

(十六)SpringBoot的AOP支持

更多内容请查阅分类专栏:SpringBoot源码解读与原理分析

第6章主要梳理:SpringBoot准备容器与环境。主要内容包括:

  • SpringApplication的创建流程解析;
  • SpringApplication的启动阶段;
  • 内置IOC容器的创建流程;
  • SpringApplication的事件回调机制。
  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰色孤星A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值