Spring AOP核心原理分析

Spring AOP 核心原理:从思想到源码的深度剖析

AOP(面向切面编程)是 Spring 框架三大核心思想之一,与 IoC 相辅相成,共同支撑起 Spring 灵活、低耦合的架构设计。AOP 通过 "横切" 的方式分离业务逻辑与通用功能(如日志、事务、权限),彻底解决了代码冗余和耦合问题。

本次学习将从 AOP 设计思想出发,通过手写简易 AOP 理解核心逻辑,再深入 Spring AOP 源码,以掌握AOP其实现原理。

一、AOP 设计思想

1. Spring 的三大核心:IoC、DI 与 AOP

Spring 框架的强大源于三大核心思想的协同作用:

  1. IoC(控制反转):将对象创建权交给容器,解耦对象依赖;
  2. DI(依赖注入): 容器自动为对象注入依赖,简化对象组装;
  3. AOP(面向切面编程): 分离通用逻辑与业务逻辑,实现代码复用与集中管理。

三者关系:IoC 是基础,DI 是 IoC 的实现方式,AOP 基于 IoC 容器实现横切逻辑的织入,共同目标是降低耦合度

2. AOP 是思想,Spring AOP 是实现

AOP(Aspect-Oriented Programming)是一种编程思想 ,核心是 "将通用逻辑从业务逻辑中抽离,通过横切技术织入到目标方法中"。
思想层面: AOP 不依赖具体框架,任何语言都可实现(如 AspectJ、JBoss AOP)。
实现层面: Spring AOP 是 AOP 思想的具体实现,基于动态代理技术,与 Spring IoC 容器深度集成,简化了 AOP 的使用。

3. 核心价值:隔离业务逻辑,降低耦合

传统开发中,通用功能(如日志、事务)会嵌入到业务代码中,导致:

  1. 代码冗余: 同一逻辑在多个业务方法中重复编写;
  2. 耦合严重: 修改通用逻辑需改动所有业务代码;
  3. 维护困难: 业务逻辑与通用逻辑混杂,可读性差。

AOP 通过 "切面" 将通用逻辑集中管理,业务代码只关注核心逻辑,实现 "高内聚、低耦合"。

4. AOP 本质:对重复代码的集中处理

AOP 的本质是将多处重复的代码(横切逻辑)抽离为 "切面",在不修改原有业务代码的前提下,自动织入到目标方法的指定位置(如方法前、方法后)

举例:用户模块的 "新增用户""删除用户" 方法都需要日志记录和权限校验,AOP 可将这两个逻辑抽离为切面,自动织入到目标方法中,业务方法只需保留核心逻辑。

二、手写实现 AOP 的核心逻辑

为理解 AOP 核心原理,我们手动实现一个简易 AOP 框架,聚焦 "如何识别目标方法、如何织入增强逻辑、如何生成代理对象" 三大核心问题。

1. 整体流程划分

AOP 运行可分为两个阶段:
启动阶段: 解析配置(如注解),确定需要增强的目标类、目标方法,以及要织入的增强逻辑(通知),最终生成代理对象。
调用阶段: 当代理对象的方法被调用时,触发增强逻辑与目标方法的执行(按顺序织入)。

2. AOP 启动流程

核心问题
问题 1:哪些类需要生成代理?哪些方法需要织入增强逻辑?

答:通过注解(如 @Aspect 标记切面类,@Before 标记前置通知)指定目标类和方法。
问题 2:方法前后需要织入什么样的逻辑?

答:切面类中被 @Before @After 等注解标记的方法,即为增强逻辑(通知)。
问题 3:如何绑定目标方法与增强逻辑?

答:通过 "切入点表达式"(如 execution(* com.example.service.. (...)))匹配目标方法,建立 "目标方法 → 增强逻辑链" 的映射关系。
问题 4:如何将目标对象转为代理对象?

答:使用 JDK 动态代理(针对接口)或 CGLIB 代理(针对类),生成代理对象替换原始对象。

启动流程实现步骤
步骤 1:定义核心注解(模拟 Spring AOP 注解)

java 复制代码
// 标记切面类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
}

// 标记前置通知(目标方法执行前)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {
    String value(); // 切入点表达式,如 "com.example.service.UserService.add()"
}

// 标记后置通知(目标方法执行后)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface After {
    String value();
}

步骤 2:定义通知接口(封装增强逻辑)

java 复制代码
// 通知接口,不同类型的通知实现此接口
public interface Advice {
    void execute(Method method, Object[] args, Object target);
}

// 前置通知实现
public class BeforeAdvice implements Advice {
    private Object aspect; // 切面实例
    private Method adviceMethod; // 增强方法(如日志逻辑)

    public BeforeAdvice(Object aspect, Method adviceMethod) {
        this.aspect = aspect;
        this.adviceMethod = adviceMethod;
    }

    @Override
    public void execute(Method method, Object[] args, Object target) {
        try {
            // 调用切面中的增强方法(无参数简化版)
            adviceMethod.invoke(aspect);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 后置通知实现(类似)
public class AfterAdvice implements Advice {
    // 实现与 BeforeAdvice 类似,略
}

步骤 3:解析切面类,绑定目标方法与通知

java 复制代码
public class AopConfig {
    // 存储目标方法与通知链的映射:key为"类名.方法名",value为通知列表
    private Map<String, List<Advice>> adviceMap = new HashMap<>();

    public void parseAspect(Object aspect) throws Exception {
        Class<?> aspectClass = aspect.getClass();
        if (!aspectClass.isAnnotationPresent(Aspect.class)) {
            return; // 非切面类,跳过
        }

        // 遍历切面类的所有方法,解析 @Before @After 注解
        for (Method method : aspectClass.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Before.class)) {
                Before before = method.getAnnotation(Before.class);
                String pointcut = before.value(); // 切入点表达式(如 "UserService.add")
                // 将通知添加到映射中
                addAdvice(pointcut, new BeforeAdvice(aspect, method));
            } else if (method.isAnnotationPresent(After.class)) {
                After after = method.getAnnotation(After.class);
                String pointcut = after.value();
                addAdvice(pointcut, new AfterAdvice(aspect, method));
            }
        }
    }

    private void addAdvice(String pointcut, Advice advice) {
        // 简化处理:直接用切入点字符串作为key(实际应解析表达式匹配方法)
        adviceMap.computeIfAbsent(pointcut, k -> new ArrayList<>()).add(advice);
    }

    // 根据目标方法获取通知链
    public List<Advice> getAdvices(Method method) {
        String key = method.getDeclaringClass().getSimpleName() + "." + method.getName();
        return adviceMap.getOrDefault(key, new ArrayList<>());
    }
}

步骤 4:生成代理对象

使用 JDK 动态代理生成代理对象,代理对象调用方法时会触发通知链:

java 复制代码
public class ProxyFactory {
    private AopConfig config;

    public ProxyFactory(AopConfig config) {
        this.config = config;
    }

    // 为目标对象创建代理
    public Object createProxy(Object target) {
        Class<?> targetClass = target.getClass();
        // JDK 动态代理:需要目标类实现接口
        return Proxy.newProxyInstance(
                targetClass.getClassLoader(),
                targetClass.getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 1. 获取当前方法的通知链
                        List<Advice> advices = config.getAdvices(method);
                        // 2. 执行通知链和目标方法
                        return new AdviceChain().proceed(method, args, target, advices);
                    }
                }
        );
    }
}

启动流程总结
获取配置: 扫描并解析 @Aspect @Before 等注解,提取切面信息;
解析切面: 将切面中的增强方法封装为 Advice(前置 / 后置通知);
绑定关系: 通过切入点表达式建立 "目标方法 → 通知链" 的映射;
生成代理: 为目标对象创建代理对象,代理对象持有通知链信息。

3. AOP 调用流程

核心问题
问题 1:如何将增强逻辑织入目标方法?

答:代理对象的 invoke 方法拦截目标方法调用,先执行增强逻辑,再执行目标方法。
问题 2:多个增强方法的执行顺序如何保证?

答:通过 "索引 + 递归" 遍历通知链,按顺序执行前置通知 → 目标方法 → 后置通知。

调用流程实现:通知链执行

java 复制代码
public class AdviceChain {
    // 递归执行通知链
    public Object proceed(Method method, Object[] args, Object target, List<Advice> advices) throws Throwable {
        return proceed(0, method, args, target, advices);
    }

    private Object proceed(int index, Method method, Object[] args, Object target, List<Advice> advices) throws Throwable {
        // 若所有通知执行完毕,调用目标方法
        if (index == advices.size()) {
            return method.invoke(target, args);
        }
        // 执行当前通知,递归执行下一个通知
        Advice advice = advices.get(index);
        advice.execute(method, args, target);
        return proceed(index + 1, method, args, target, advices);
    }
}

执行顺序:

前置通知按注册顺序执行 → 目标方法执行 → 后置通知按注册顺序执行(简化版,实际 Spring 有更复杂的排序逻辑)。

调用流程总结
拦截调用: 请求调用代理对象的方法,触发 InvocationHandler.invoke();
获取通知链: 根据目标方法从映射中获取对应的通知链;
执行链织入: 通过递归按顺序执行所有通知和目标方法。

4. 手写实现整体流程

启动阶段: 解析切面注解,绑定 "目标方法 - 通知链" 关系,生成代理对象替换原始对象;
调用阶段: 代理对象拦截方法调用,通过 "索引 + 递归" 执行通知链和目标方法;
核心映射: 启动时建立的 "方法 - 通知" 映射是调用时快速获取增强逻辑的关键;
简化说明: 手写实现未支持多切面和复杂切入点表达式,Spring AOP 对此做了完善。

三、Spring AOP 源码分析

Spring AOP 基于 IoC 容器,通过动态代理实现增强逻辑的织入,核心类包括 @EnableAspectJAutoProxy、AnnotationAwareAspectJAutoProxyCreator、ProxyFactory 等。

1. AOP 在 Spring 源码中的位置

Spring AOP 的核心动作发生在Bean 初始化后:当 IoC 容器创建 Bean 时,若该 Bean 被切面匹配,会自动为其生成代理对象,最终存入容器的是代理对象而非原始对象。

2. @EnableAspectJAutoProxy 注解的作用

@EnableAspectJAutoProxy 是开启 Spring AOP 的开关,通过导入 AspectJAutoProxyRegistrar 注册 AOP 核心组件:

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AspectJAutoProxyRegistrar.class) // 导入注册器
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false; // 是否强制使用CGLIB代理(默认JDK动态代理)
    boolean exposeProxy() default false; // 是否暴露代理对象(解决自调用问题)
}

AspectJAutoProxyRegistrar 会向容器注册 AnnotationAwareAspectJAutoProxyCreator(核心处理器):

java 复制代码
public class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 注册 AnnotationAwareAspectJAutoProxyCreator
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        // 处理 proxyTargetClass 和 exposeProxy 属性
        // ...
    }
}

3. AnnotationAwareAspectJAutoProxyCreator 的执行时机

AnnotationAwareAspectJAutoProxyCreator 是 Spring AOP 的核心处理器,实现了 BeanPostProcessor 接口,在Bean 初始化后(postProcessAfterInitialization 方法)为符合条件的 Bean 生成代理对象。

执行流程:

  1. 扫描切面: 容器启动时,扫描所有 @Aspect 注解的切面类;
  2. 解析通知: 将切面中的 @Before @After 等注解方法解析为 Advisor(通知器,包含切入点和通知);
  3. 匹配目标: 对每个初始化后的 Bean,检查是否被任何 Advisor 的切入点匹配;
  4. 生成代理: 若匹配,通过 ProxyFactory 生成代理对象。

4. Spring AOP 如何生成代理对象

代理对象的生成由 ProxyFactory 负责,核心逻辑在 getProxy() 方法:

java 复制代码
public class ProxyFactory extends ProxyCreatorSupport {
    public Object getProxy() {
        return createAopProxy().getProxy();
    }

    protected final synchronized AopProxy createAopProxy() {
        if (!this.active) {
            activate();
        }
        // 根据目标类和配置选择代理方式
        return getAopProxyFactory().createAopProxy(this);
    }
}

AopProxyFactory 选择代理方式的逻辑:

  1. 若目标类实现接口,默认使用 JdkDynamicAopProxy(JDK 动态代理);
  2. 若目标类未实现接口或 proxyTargetClass = true,使用 CglibAopProxy(CGLIB 代理)。

5. 代理对象的调用流程

当代理对象的方法被调用时,JDK 代理会触发 JdkDynamicAopProxy.invoke() 方法,CGLIB 代理会触发 CglibAopProxy.intercept() 方法,核心逻辑一致:

java 复制代码
// JdkDynamicAopProxy 的 invoke 方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    try {
        // 1. 检查是否需要暴露代理对象(解决自调用问题)
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        // 2. 获取目标对象
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);

        // 3. 获取当前方法的拦截器链(通知链)
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        // 4. 若没有拦截器,直接调用目标方法
        if (chain.isEmpty()) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
        } else {
            // 5. 创建 MethodInvocation,执行拦截器链
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            retVal = invocation.proceed(); // 执行链
        }

        // 6. 处理返回值
        // ...
        return retVal;
    } finally {
        // 清理资源
        // ...
    }
}

拦截器链执行: ReflectiveMethodInvocation.proceed() 采用 "索引 + 递归" 模式,与手写实现的 AdviceChain 逻辑类似,按顺序执行所有通知和目标方法。

四、Spring 如何实现多层代理

当一个 Bean 被多个切面 增强时,Spring 会生成多层代理 (代理的代理),每层代理对应一个切面的增强逻辑。
原理
多层代理生成: 第一个切面为原始对象生成代理 1,第二个切面为代理 1 生成代理 2,以此类推,最终容器中存储的是最外层代理;
调用顺序: 外层代理先执行自身切面的通知,再调用内层代理,直到原始对象的方法执行,然后按相反顺序执行后置通知。

举例:A 被切面 1 和切面 2 增强,生成代理 2(外层,切面 2)→ 代理 1(内层,切面 1)→ 原始对象 A。

调用流程:

代理2.invoke() → 切面 2 前置通知 → 代理1.invoke() → 切面 1 前置通知 → A.方法() → 切面 1 后置通知 → 切面 2 后置通知。

源码支撑

多层代理由 AnnotationAwareAspectJAutoProxyCreator 循环处理实现:

  1. 每次为 Bean 生成代理后,会再次检查代理对象是否被其他切面匹配;
  2. 若匹配,以当前代理对象为目标,生成新的代理对象,直到没有匹配的切面。

核心逻辑在 AbstractAutoProxyCreator.postProcessAfterInitialization():

java 复制代码
@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;
}

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // 检查是否需要代理
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // 获取匹配的通知器(Advisor)
    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;
}

每次调用 createProxy 都会基于当前对象(可能是原始对象或已有代理)生成新的代理,从而实现多层代理。

总结

Spring AOP 的核心是通过动态代理切面织入,实现通用逻辑与业务逻辑的分离。从思想到实现,其核心流程可归纳为:

  1. 设计思想: AOP 作为横切编程思想,解决代码冗余和耦合问题,Spring AOP 是其成熟实现;
  2. 手写实现: 通过解析配置、绑定 "方法 - 通知" 关系、生成代理对象,模拟 AOP 核心逻辑,关键是 "代理拦截 + 通知链执行";
  3. Spring 源码: @EnableAspectJAutoProxy 开启AOP,AnnotationAwareAspectJAutoProxyCreator 负责代理生成,ProxyFactory选择代理方式,MethodInvocation 执行通知链;
  4. 多层代理: 通过循环生成代理,实现多切面增强,调用时从外层到内层依次执行。

理解 AOP 原理不仅能帮助我们灵活使用 @Aspect 等注解,更能在复杂场景下(如自定义切面、解决自调用问题)写出更优雅的代码。AOP 与 IoC 的结合,正是 Spring 框架强大灵活性的根源。

相关推荐
霸道流氓气质4 小时前
SpringBoot+MybatisPlus+自定义注解+切面实现水平数据隔离功能(附代码下载)
java·spring boot·后端
海边夕阳20064 小时前
深入解析volatile关键字:多线程环境下的内存可见性与指令重排序防护
java·开发语言·jvm·架构
ZeroKoop4 小时前
JDK版本管理工具JVMS
java·开发语言
无敌最俊朗@4 小时前
SQLite 核心知识点讲解
jvm·数据库·oracle
rengang664 小时前
101-Spring AI Alibaba RAG 示例
java·人工智能·spring·rag·spring ai·ai应用编程
小宋10214 小时前
Neo4j-图数据库入门图文保姆攻略
数据库·neo4j
乾坤瞬间4 小时前
【Java后端进行ai coding实践系列二】记住规范,记住内容,如何使用iflow进行上下文管理
java·开发语言·ai编程
迦蓝叶4 小时前
JAiRouter v1.1.0 发布:把“API 调没调通”从 10 分钟压缩到 10 秒
java·人工智能·网关·openai·api·协议归一
不知道累,只知道类4 小时前
记一次诡异的“偶发 404”排查:CDN 回源到 OSS 导致 REST API 失败
java·云原生