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 框架强大灵活性的根源。

相关推荐
云游6 小时前
Jaspersoft Studio community edition 7.0.3的应用
java·报表
帅气的你6 小时前
Spring Boot 集成 AOP 实现日志记录与接口权限校验
java·spring boot
zhglhy6 小时前
Spring Data Slice使用指南
java·spring
win x7 小时前
Redis 主从复制
java·数据库·redis
周末吃鱼7 小时前
MySQL CTE:SQL查询新模式
数据库·sql·mysql
木风小助理7 小时前
解读 SQL 累加计算:从传统方法到窗口函数
大数据·数据库·sql
weixin_423995007 小时前
unity 处理图片:截图,下载,保存
java·unity·游戏引擎
帅气的你7 小时前
从零封装一个通用的 API 接口返回类:统一前后端交互格式
java·设计模式
qq_178057077 小时前
基于minio实现的分片上传-支持断点续传
java
高山上有一只小老虎7 小时前
灵异背包?
java·算法