Spring AOP源码-动态代理与切面编程

作为Spring的几大核心之一,AOP是我们绕不开的一个重点。AOP也是Spring很优雅的一块内容,那到底AOP是什么呢?以及它的原理是什么呢?有很多问题我们要关注,那不如我们先从具体的场景出发来看看AOP解决了什么问题?

首先,AOP是解决了非业务代码入侵业务代码的问题,那怎么理解呢?我们平时在写各种方法的时候经常会碰到这种情况:

csharp 复制代码
public class OrderService {
    public void createOrder(Order order) {
        // 非业务代码:日志记录
        System.out.println("[LOG] 开始创建订单,订单ID: " + order.getId());
        long startTime = System.currentTimeMillis();

        try {
            // 业务代码
            validateOrder(order);
            saveOrderToDB(order);
            notifyWarehouse(order);

            // 非业务代码:日志记录
            System.out.println("[LOG] 订单创建成功,耗时: " 
                + (System.currentTimeMillis() - startTime) + "ms");
        } catch (Exception e) {
            // 非业务代码:异常日志
            System.err.println("[ERROR] 订单创建失败: " + e.getMessage());
            throw e;
        }
    }
}

日志的代码散落在各个业务方法里,你如果想去改动日志的格式,那可就麻烦了。所以AOP闪亮登场:

java 复制代码
@Aspect
@Component
public class LogAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object logMethodExecution(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        System.out.println("[LOG] 调用方法: " + methodName + ",参数: " + Arrays.toString(args));
        long startTime = System.currentTimeMillis();

        try {
            Object result = pjp.proceed(); // 执行目标方法
            System.out.println("[LOG] 方法执行成功,耗时: " 
                + (System.currentTimeMillis() - startTime) + "ms");
            return result;
        } catch (Exception e) {
            System.err.println("[ERROR] 方法执行失败: " + e.getMessage());
            throw e;
        }
    }
}

// 业务代码纯净版
public class OrderService {
    public void createOrder(Order order) {
        validateOrder(order);
        saveOrderToDB(order);
        notifyWarehouse(order);
    }
}

这样我们在修改日志代码的时候就无需这么复杂了,那这时候就有人该说了,就这点用? NONONONO,我们要知道AOP实际上最重要的是其思想,这个思想已经运用到我们经常用的一些地方了,比如事物管理、权限检验等......但我们只需要了解实际上AOP的重要思想就是 动态代理

1. Spring AOP的核心架构

Spring AOP的核心也是围绕着多个接口和类来实现的。

1.1 AopProxy

这个接口是顶级接口,定义了代理对象的获取方式。

csharp 复制代码
public interface AopProxy {
    Object getProxy();
}

实现类:JDK动态代理、CGLIB动态代理

1.2 Advised

这个接口是表示可被代理的对象,包含了代理的配置信息

csharp 复制代码
public interface Advised {
    TargetSource getTargetSource();
    Advisor[] getAdvisors();
    void addAdvisor(Advisor advisor);
    ...
}

实现类:ProxyFactory(也实现AopProxy)作用是整合代理配置信息然后再生成对应的代理对象

1.3 Advisor

通知器的接口,是Advice和Pointcut组合在一起的。

csharp 复制代码
public interface Advisor {
    Advice getAdvice();
    boolean isPerInstance();
}

实现类:

◦ PointcutAdvisor:最常用的 Advisor 类型,包含 Pointcut 和 Advice

◦ AbstractPointcutAdvisor:PointcutAdvisor 的抽象实现

◦ DefaultPointcutAdvisor:最常用的具体实现

在具体的使用上,一般就是声明切面。这里很多小伙伴应该对通知的概念有些模糊,所谓的"通知"意思就是程序在执行特定的Join Point时应该采取的动作或行为。也是AOP框架在特定切入点插入的一些增强代码。

其他的接口例如,Advice和PointCut一个是通知接口一个是切点接口,这里Advice接口具体实现类就有我们经常说那几种通知,前置、后置、环绕等。

2. 代理生成的决策逻辑

我们要知道,Spring AOP是根据目标对象的特性来选择代理方式,我们来看这一段的源码

arduino 复制代码
// DefaultAopProxyFactory.java
public AopProxy createAopProxy(AdvisedSupport config) {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config); // JDK 代理
        }
        return new ObjenesisCglibAopProxy(config); // CGLIB 代理
    } else {
        return new JdkDynamicAopProxy(config);
    }
}

我们这里可以看到,具体的创建逻辑,是JDK代理还是CGLIB代理我们是看目标类是否实现了接口,并且没有强制要求CGLIB,则这时候我们采用JDK代理。否则是使用CGLIB代理,你也可以强行使用CGLIB代理。 @EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用 CGLIB

这一块的内容还有些判断,就是要扫描类是否为基础设施类,这些是不能被代理的,然后判断当前类是否为用户排除的以及是否为 @Aspect 本身。

3. AOP与Spring IoC容器的整合

Spring AOP 与 Spring IoC 容器的整合,让 AOP 的增强能力无缝地融入到 Bean 的生命周期中,从而实现了所谓的 "面向切面编程天然具备依赖注入能力" 。那具体的整合点在哪呢?

3.1 Bean后置处理器

Spring AOP 的核心机制,是通过 Bean 的后置处理器(BeanPostProcessor) 来实现与 IoC 容器的整合。在容器初始化过程中,Spring 会注册一系列 BeanPostProcessor,其中最关键的是:

复制代码
AnnotationAwareAspectJAutoProxyCreator

它是Spring AOP 的"代理生成器",也是一个特殊的 BeanPostProcessor,用于:在 IoC 容器实例化完 Bean 后,判断是否需要创建代理,并将增强逻辑织入 Bean 中。

3.2 生命周期中的整合流程

我们知道Bean的生命周期简单来看大概就是下面这个过程:

实例化->依赖注入->初始化(回调)->Bean Ready 所以AOP发生在哪呢?

实际上AOP代理增强就是在初始化之后,正式交付之前,这一步就是通过上面的AnnotationAwareAspectJAutoProxyCreator 来判断是否生成代理。这个类的来龙去脉我们要说一下,实际上这个类不是你写个@Aspect就冒出来的,而是在容器初始化时注册出来的。在你加上 @EnableAspectJAutoProxy 后,Spring 会通过 Import 机制注册一个 AspectJAutoProxyRegistrar ,最终把 AnnotationAwareAspectJAutoProxyCreator 加入 BeanFactory。这样它就参与到所有 Bean 的后置处理中了。

3.3 代理增强如何"织入Bean中"呢?

这里是整合的关键:

typescript 复制代码
public Object postProcessAfterInitialization(Object bean, String beanName) {
    // 判断是否需要代理
    Object wrappedBean = wrapIfNecessary(bean, beanName);
    return wrappedBean != null ? wrappedBean : bean;
}

通过切点匹配Advisor判断是否要增强,需要增强->创建代理->返回代理对象,否则就原样返回。所以最终放回IoC容器的,其实是被代理的Bean,也就是AOP增强过的。所以我们可以看出,AOP和IoC是共用一个容器:

  • AOP织入发生在容器中,受IoC生命周期管理
  • 被代理的Bean其他不受影响,依旧可以是单例、可以注入、支持生命周期回调

这让Spring AOP完全嵌入到IoC到统一管理流程中。

4. 总结

到这里我们基本上可以总结出AOP的核心思想是什么了,如果用一句话概括,那我们可以这样描述:在不修改原始业务逻辑的前提下,将横切关注点以统一的方式织入目标对象的运行流程中

另外多说一嘴,在具体的方法调用上,被抽象成拦截器链。通过 MethodInvocationproceed() 方法递归触发链式调用,实现通知(Advice)的顺序执行。这里责任链模式我后面会单独拿来说。这篇文章就说到这里,下篇文章我们就可以探讨JDK 与 CGLIB 动态代理的抉择与实现。

相关推荐
Hanson Huang1 小时前
【数据结构】堆排序详细图解
java·数据结构·排序算法·堆排序
软件测试曦曦1 小时前
16:00开始面试,16:08就出来了,问的问题有点变态。。。
自动化测试·软件测试·功能测试·程序人生·面试·职场和发展
路在脚下@1 小时前
Redis实现分布式定时任务
java·redis
xrkhy1 小时前
idea的快捷键使用以及相关设置
java·ide·intellij-idea
巨龙之路1 小时前
Lua中的元表
java·开发语言·lua
拉不动的猪2 小时前
设计模式之------策略模式
前端·javascript·面试
独行soc2 小时前
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
linux·运维·服务器·前端·面试·职场和发展·csrf
花花鱼2 小时前
itext7 html2pdf 将html文本转为pdf
java·pdf
uhakadotcom2 小时前
Google Earth Engine 机器学习入门:基础知识与实用示例详解
前端·javascript·面试
小丁爱养花2 小时前
驾驭 Linux 云: JavaWeb 项目安全部署
java·linux·运维·服务器·spring boot·后端·spring