Spring AOP:简化动态代理使用,支持声明式切面。

Spring AOP(面向切面编程)的实现基于动态代理技术,通过在运行时动态生成代理对象来拦截目标方法,并在方法执行前后插入切面逻辑(如日志、事务管理等)。其核心实现机制如下:


1. 核心实现技术

Spring AOP 主要依赖两种动态代理技术:

  • JDK 动态代理:基于接口的代理,要求目标对象实现至少一个接口。
  • CGLIB 动态代理:基于子类的代理,通过生成目标类的子类来拦截方法(适用于无接口的类)。

Spring 默认优先使用 JDK 动态代理,若目标类未实现接口,则自动切换到 CGLIB。


2. 核心组件与流程

(1) 切面(Aspect)与通知(Advice)

  • 切面 :通过 @Aspect 注解定义的类,包含多个通知方法。
  • 通知类型
    • @Before:方法执行前执行。
    • @After:方法执行后执行(无论是否异常)。
    • @AfterReturning:方法正常返回后执行。
    • @AfterThrowing:方法抛出异常后执行。
    • @Around:包裹目标方法,可自定义执行逻辑。

(2) 切入点(Pointcut)

  • 通过表达式(如 execution(* com.example.service.*.*(..)))定义哪些方法需要被拦截。
  • 使用 @Pointcut 注解定义可重用的切入点。

(3) 代理对象的生成

  1. 解析切面 :Spring 容器启动时,解析所有 @Aspect 注解的类,提取通知和切入点。
  2. 创建 Advisor :将通知和切入点组合为 Advisor 对象(一个 Advisor 对应一个通知和一个切入点)。
  3. 生成代理
    • 对目标对象,根据是否有接口选择 JDK 或 CGLIB 代理。
    • 代理对象会拦截目标方法,根据切入点匹配决定是否执行通知逻辑。

(4) 拦截器链(Interceptor Chain)

  • 当目标方法被调用时,代理对象会将所有匹配的 Advisor 转换为拦截器(MethodInterceptor)。
  • 拦截器按顺序形成链式调用(责任链模式),依次执行通知逻辑,最终调用目标方法。

3. 示例流程(@Around 为例)

java 复制代码
@Aspect
public class LoggingAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Before method: " + joinPoint.getSignature());
        Object result = joinPoint.proceed(); // 调用目标方法
        System.out.println("After method: " + joinPoint.getSignature());
        return result;
    }
}
  1. Spring 创建目标 Service 的代理对象。
  2. 调用代理对象的方法时,触发 MethodInterceptor(即 logMethod)。
  3. 拦截器执行 @Around 逻辑,控制目标方法的执行。

4. 实现细节

  • JDK 动态代理 :通过 java.lang.reflect.ProxyInvocationHandler 实现。

    java 复制代码
    public class JdkProxy implements InvocationHandler {
        private Object target;
        public Object bind(Object target) {
            this.target = target;
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                                        target.getClass().getInterfaces(), 
                                        this);
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 执行通知逻辑
            return method.invoke(target, args);
        }
    }
  • CGLIB 动态代理 :通过 EnhancerMethodInterceptor 生成子类。

    java 复制代码
    public class CglibProxy implements MethodInterceptor {
        public Object getProxy(Class<?> clazz) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);
            return enhancer.create();
        }
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            // 执行通知逻辑
            return proxy.invokeSuper(obj, args);
        }
    }

5. 局限性与注意事项

  • 局限性
    • 只能拦截 Spring 容器管理的 Bean 的方法调用。
    • 无法拦截私有方法、静态方法或非 Spring 管理的对象。
  • 性能:CGLIB 生成代理的速度较慢,但调用效率与 JDK 代理接近。
  • 与 AspectJ 对比:Spring AOP 更轻量但功能较弱;AspectJ 提供更强大的切面功能(如字段拦截、编译时织入)。

6.1 核心流程的时序图(使用 Mermaid 语法),

展示了从 Spring 容器启动到方法拦截的完整过程:

sequenceDiagram participant Spring容器 participant Aspect解析器 participant ProxyFactory participant ProxyObject participant InterceptorChain participant TargetObject participant Client Note over Spring容器: 1. Spring 容器启动阶段 Spring容器->>Aspect解析器: 扫描所有@Aspect类 Aspect解析器->>Aspect解析器: 解析通知和切入点 Aspect解析器->>Spring容器: 生成Advisor列表 Note over Spring容器: 2. Bean 初始化阶段 Spring容器->>ProxyFactory: 创建目标Bean的代理 alt 目标类有接口 ProxyFactory-->>ProxyObject: JDK动态代理 else 目标类无接口 ProxyFactory-->>ProxyObject: CGLIB动态代理 end Spring容器->>Client: 注入代理对象(而非原始对象) Note over Client: 3. 方法调用阶段 Client->>ProxyObject: 调用目标方法(如userService.save()) activate ProxyObject ProxyObject->>InterceptorChain: 触发拦截器链 activate InterceptorChain loop 按顺序执行拦截器 InterceptorChain->>@Around Advice: 执行前置逻辑 @Around Advice-->>InterceptorChain: 是否继续? end InterceptorChain->>TargetObject: 调用原始目标方法 activate TargetObject TargetObject-->>InterceptorChain: 方法返回结果 deactivate TargetObject loop 按逆序执行后置拦截器 InterceptorChain->>@AfterReturning Advice: 执行后置逻辑 @AfterReturning Advice-->>InterceptorChain: 处理结果 end InterceptorChain-->>ProxyObject: 返回最终结果 deactivate InterceptorChain ProxyObject-->>Client: 返回结果 deactivate ProxyObject

6.2 时序图关键步骤说明

阶段 1:Spring 容器启动

  1. 扫描切面 :Spring 容器扫描所有 @Aspect 注解的类。
  2. 解析通知和切入点 :提取 @Before@Around 等通知方法,以及 @Pointcut 定义的切入点。
  3. 生成 Advisor :将通知和切入点组合为 Advisor(每个 Advisor 对应一个切入点和一个通知)。

阶段 2:Bean 初始化

  1. 创建代理对象
    • 如果目标类实现了接口,使用 JDK 动态代理生成代理。
    • 如果目标类无接口,使用 CGLIB 生成子类代理。
  2. 依赖注入:向客户端(如 Controller)注入代理对象,而非原始对象。

阶段 3:方法调用

  1. 代理对象拦截 :客户端调用代理对象的方法(如 userService.save())。
  2. 拦截器链执行
    • 前置处理 :按顺序执行 @Around 的前置逻辑和 @Before
    • 目标方法调用:通过反射调用原始目标方法。
    • 后置处理 :逆序执行 @Around 的后置逻辑、@AfterReturning@After 等。
  3. 返回结果:最终结果通过代理对象返回给客户端。

关键设计模式

  • 动态代理模式:通过代理对象拦截方法调用。
  • 责任链模式:拦截器链按顺序执行多个通知。
  • 适配器模式 :将 Advisor 适配为 MethodInterceptor

常见问题

  1. 为什么私有方法无法被 AOP 拦截?

    动态代理只能拦截公有方法,私有方法不会被代理对象重写。

  2. 如何强制使用 CGLIB 代理?

    @EnableAspectJAutoProxy 中设置 proxyTargetClass = true

  3. AOP 失效的场景有哪些?

    • 同类方法内部调用(绕过代理,直接调用原始方法)。
    • 静态方法或非 Spring 管理的对象。

通过时序图可以清晰看到 Spring AOP 从代理对象的生成到拦截器链执行的全流程,理解其如何将横切逻辑(如日志、事务)与业务代码解耦。

总结

Spring AOP 通过动态代理在运行时生成代理对象,结合切面、通知和切入点的定义,实现了非侵入式的横切关注点(如日志、事务)的模块化。其灵活性和易用性使得它成为 Spring 生态中不可或缺的组件。

相关推荐
习惯就好zz2 分钟前
Kotlin标准函数库学习
java·学习·kotlin
知识分享小能手29 分钟前
CSS3学习教程,从入门到精通,CSS3 选择器权重问题语法知识点及案例代码(5)
java·前端·css·学习·html·css3·html5
whysqwhw37 分钟前
TCP与UDP
java
行思理1 小时前
PHP、Java、Go、Python、Node.js、Ruby 写的接口,服务器承载量对比
java·golang·php
神仙别闹1 小时前
基于Java(Springboot+Gradle+Mybatis+templeaf 框架)+Mysql构建的(Web)校园二手平台系统
java·spring boot·mybatis
w_t_y_y1 小时前
IntelliJ 配置文件plugin.xml
xml·java·开发语言
敖云岚1 小时前
【Spring】第三弹:基于 XML 获取 Bean 对象
xml·java·spring
tan_jianhui1 小时前
用Maven创建只有POM文件的项目
java·数据库·maven
盖世英雄酱581361 小时前
设计模式在Springboot都用在哪些地方呢
java·后端
逸风尊者2 小时前
开发易忽视的问题:内存溢出/泄漏案例
java·后端·面试