注解式 AOP 实现和运行机制的完整分析

Spring AOP 源码解析:注解式切面增强机制

IoC 和 AOP 被称为 Spring 两大基础模块,支撑着上层扩展的实现和运行。虽然 AOP 同样建立在 IoC 的实现基础之上,但是作为对 OOP(Object-Oriented Programing) 的补充,AOP(Aspect-Oriented Programming) 在程序设计领域拥有其不可替代的适用场景和地位。Spring AOP 作为 AOP 思想的实现,被誉为 Spring 框架的基础模块也算是实至名归。Spring 在 1.0 版本的时候就引入了对 AOP 的支持,并且随着版本的迭代逐渐提供了基于 XML 配置、注解,以及 schema 配置的使用方式,考虑到实际开发中使用注解配置的方式相对较多,所以本文主要分析注解式 AOP 的实现和运行机制。

注解式 AOP 示例

首先我们还是通过一个简单的示例演示一下注解式 AOP 的具体使用。假设我们声明了一个 IService 接口,并提供了相应的实现类 ServiceImpl,如下:

public interface IService {
    void sayHello();
    void sayHelloTo(String name);
    void sayByebye();
    void sayByebyeTo(String name);
}

@Service
public class ServiceImpl implements IService {

    @Override
    public void sayHello() {
        this.sayHelloTo("zhenchao");
    }

    @Override
    public void sayHelloTo(String name) {
        System.out.println("hello, " + name);
    }

    @Override
    public void sayByebye() {
        this.sayByebyeTo("zhenchao");
    }

    @Override
    public void sayByebyeTo(String name) {
        System.out.println("byebye, " + name);
    }

}

现在我们希望借助 Spring AOP 实现对方法调用的打点功能。首先我们需要定义一个切面:

@Aspect
@Component
public class MetricAspect {

    @Before("execution(* sayHello*(..))")
    public void beforeMetrics4sayHello(JoinPoint point) {
        System.out.println("[BEFORE] metrics for method: " + point.getSignature().getName());
    }

    @Around("execution(* say*(..))")
    public Object aroundMetrics4say(ProceedingJoinPoint point) throws Throwable {
        System.out.println("[AROUND] before metrics for method: " + point.getSignature().getName());
        Object obj = point.proceed();
        System.out.println("[AROUND] after metrics for method: " + point.getSignature().getName());
        return obj;
    }

    @After("execution(* sayByebye*(..))")
    public void afterMetrics4sayByebye(JoinPoint point) {
        System.out.println("[AFTER] metrics for method: " + point.getSignature().getName());
    }

}

通过 @Aspect 注解标记 MetricAspect 是一个切面,通过注解 @Before@After,以及 @Around,我们在切面中定义了相应的前置、后置,以及环绕增强。然后我们需要在 XML 配置中添加一行如下配置以启用注解式 AOP:

<aop:aspectj-autoproxy/>

现在,我们就算大功告成了。

当然,上面的实现只是注解式 AOP 使用的一个简单示例,并没有覆盖所有的特性。对于 Spring AOP 特性的介绍不属于本文的范畴,不过我们还是会在下面分析源码的过程中进行针对性的介绍。

注解式 AOP 实现机制

下面从启用注解式 AOP 的那一行配置切入,即 <aop:aspectj-autoproxy/> 标签。前面在分析 Spring IoC 实现的文章中,曾专门分析过 Spring 默认标签和自定义标签的解析过程。对于一个标签而言,除了标签的定义,还需要有对应的标签的解析器,并在 Spring 启动时将标签及其解析器注册到 Spring 容器中。标签 <aop:aspectj-autoproxy /> 的注册过程由 AopNamespaceHandler#init 方法实现:

// 注册 标签及其解析器
this.registerBeanDefinitionParser(“aspectj-autoproxy”, new AspectJAutoProxyBeanDefinitionParser());

AspectJAutoProxyBeanDefinitionParser 类是标签 <aop:aspectj-autoproxy /> 的解析器,该类实现了 BeanDefinitionParser 接口,并实现了 BeanDefinitionParser#parse 接口方法,属于标准的标签解析器定义。Spring 容器在启动时会调用 AspectJAutoProxyBeanDefinitionParser#parse 方法解析标签,实现如下:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 注册标签解析器,默认使用 AnnotationAwareAspectJAutoProxyCreator
    AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
    // 解析 <aop:include /> 子标签,记录到 BeanDefinition 到 includePatterns 属性中
    this.extendBeanDefinition(element, parserContext);
    return null;
}

该方法做了两件事情:注册标签解析器和处理 <aop:include /> 子标签。本文我们重点来看标签解析器的注册过程,即 >
AopNamespaceUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法:

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {
    // 1. 注册或更新代理创建器 ProxyCreator 的 BeanDefinition 对象
    BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    // 2. 获取并处理标签的 proxy-target-class 和 expose-proxy 属性
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    // 3. 注册组件,并发布事件通知
    registerComponentIfNecessary(beanDefinition, parserContext);
}

我们在代码注释中标明了该方法所做的 3 件事情,其中 1 和 2 是我们分析的关键,首先来看 1 过程所做的事情:

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
        BeanDefinitionRegistry registry, @Nullable Object source) {
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

private static BeanDefinition registerOrEscalateApcAsRequired(
        Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

    // 如果名为 org.springframework.aop.config.internalAutoProxyCreator 的 bean 已经在册
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        // 已经在册的 ProxyCreator 与当前期望的类型不一致,则依据优先级进行选择
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            // 选择优先级高的 ProxyCreator 更新注册
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }

    // 没有对应在册的 ProxyCreator,注册一个新的
    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;
}

上述实现的逻辑还是挺简单的,即注册一个名为 org.springframework.aop.config.internalAutoProxyCreator 的 BeanDefinition,我们称之为代理创建器(ProxyCreator)。这里使用的默认实现为 AnnotationAwareAspectJAutoProxyCreator 类,如果存在多个候选实现,则选择优先级最高的进行注册。

接下来看一下过程 2,这一步主要是用来解析标签 <aop:aspectj-autoproxy/>proxy-target-classexpose-proxy 属性配置,由 AopNamespaceUtils#useClassProxyingIfNecessary 方法实现:

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
    if (sourceElement != null) {
        /*
         * 获取并处理 proxy-target-class 属性:
         * - false 表示使用 java 原生动态代理
         * - true 表示使用 CGLib 动态
         *
         * 但是对于一些没有接口实现的类来说,即使设置为 false 也会使用 CGlib 进行代理
         */
        boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
        if (proxyTargetClass) {
            // 为之前注册的 ProxyCreator 添加一个名为 proxyTargetClass 的属性,值为 true
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }

        /*
         * 获取并处理 expose-proxy 标签,实现对于内部方法调用的 AOP 增强
         */
        boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
        if (exposeProxy) {
            // 为之前注册的 ProxyCreator 添加一个名为 exposeProxy 的属性,值为 true
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
}

其中 proxy-target-class 属性用来配置是否使用 CGLib 代理,而 expose-proxy 属性则用来配置是否对内部方法调用启用 AOP 增强。属性 proxy-target-class 的作用大家应该都比较熟悉,下面介绍一下 expose-prox

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值