Spring AOP 实现原理

开头

关于 AOP 的概念和使用请看我的上一篇博客:Spring AOP:从 注解配置 到 通知执行顺序 详解


关于 Aspect 切面

在 AOP 的使用中,Aspect 切面由 Pointcut 和 Advice 组成,但是没有写到如何进行切入的,这就是本篇博客所介绍的 " Proxy " ,即 代理


Proxy 代理

首先,当在 bean 的生命周期中,spring在给 bean 执行初始化后置,即方法postProcessAfterInitialization()的时候(一般情况下是这样,如果是出现了bean循环依赖,会在实例化结束后即调用了createBeanInstance()反射方法之后依赖注入之前进行生成 Proxy),去进行 AOP 的生成。

因为初始化后置方法是每生成一个 Bean 就要执行一次这个方法,正好对应了 AOP 的需求,即为每一个需要 AOP 的 Bean 进行 面向切面编程

而就是这个时候 AOP 就会生成 Bean 的代理对象 Proxy


关于Proxy的实现方式

这里运用到了一个技术叫做 "动态代理" 。而对于动态代理的实现方式有两种,分别为 JDK 代理CGLIB 代理

为了方便理解,这里先用 "静态代理" 做演示。

1. 静态代理

定义接口
  • 首先先写一个接口。
java 复制代码
// 定义接口类 UserService
public interface UserService { 
    void saveUser(String username);
}
接口实现
  • 再写一个实现类去实现此接口。
java 复制代码
// 接口实现类 UserServiceImpl
public class UserServiceImpl implements UserService { 
    @Override 
    public void saveUser(String username) { 
        System.out.println("【真实业务】正在保存用户: " + username); 
    } 
}
静态代理类
  • 这里是静态代理的重点,静态代理类创建。
java 复制代码
// 静态代理类 UserServiceStaticProxy
public class UserServiceStaticProxy implements UserService { 
    private UserService target; 
    // 拿到目标对象 
    public UserServiceStaticProxy(UserService target) { 
        this.target = target; 
    } 
    // 运用重写的操作进行业务增强
    @Override 
    public void saveUser(String username) { 
        // 前置增强 
        System.out.println("静态代理-前置操作..."); 
        // 调用目标方法
        target.saveUser(username); 
        // 后置增强 
        System.out.println("静态代理-后置操作..."); 
    } 
}
实际使用
  • 使用静态代理进行工作
java 复制代码
// 实际的使用
public class StaticProxyTest { 
    public static void main(String[] args) { 
        // 创建一个实现类为目标类 target
        UserService target = new UserServiceImpl();
        // 创建一个代理对象 proxy 进行对 target 的代理
        UserService proxy = new UserServiceStaticProxy(target); 
        // 执行代理的方法(这是重写增强后的方法)
        proxy.saveUser("张三"); 
    } 
}

2. 动态代理 之 JDK代理

定义接口
  • 同上写一个接口。
java 复制代码
// 定义接口类 UserService
public interface UserService { 
    void saveUser(String username);
}
接口实现
  • 也是写一个实现类去实现此接口。
java 复制代码
// 接口实现类 UserServiceImpl
public class UserServiceImpl implements UserService { 
    @Override 
    public void saveUser(String username) { 
        System.out.println("真实业务..." + username); 
    } 
}
动态代理(重点)
java 复制代码
// 实现动态代理
public class JdkProxyHandler 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 { 
        // 前置增强 
        System.out.println("JDK代理-前置方法调用: " + method.getName());
        // 核心:通过反射调用目标方法 
        Object result = method.invoke(target, args); 
        // 后置增强 
        System.out.println("JDK代理-后置方法调用");
        // 返回原方法的执行结果
        return result; 
    } 
}
  • 首先对于方法 invoke() ,这是一种特殊的方法:每当调用代理 proxy 的时候,proxy 会自动执行invoke()方法 ,而这也是所谓proxy代理的核心作用。
    invoke() 的参数 proxy 指的是本身method 指的是原对象的方法arg 表是以列表的形式存放的原对象的方法所需的参数
  • 对于方法 Proxy.newProxyInstance :这里的 proxy 是一种 "伪造的实现类 " ,通过 getClassLoader()getInterfaces() 将目标的 "类的加载" 和 "接口实现" 传给了 proxy 。并通过处理器 new JdkProxyHandler(target) 的处理从而使 proxy 成功 "伪装" 成了实现了对应接口的目标类,变成了原方法的 "替身" 。
实际使用
java 复制代码
// 实际的使用
public class JdkProxyTest {
    public static void main(String[] args) { 
        JdkProxyHandler jdkProxyHandler = new JdkProxyHandler();
        // 用接口类去接收这个 proxy 代理类
        UserService userService = (UserService)jdkProxyHandler.bind(new UserServiceImpl())
        userService.saveUser("张三"); 
    } 
}
  • 对于代码 UserService userService = (UserService)jdkProxyHandler.bind(new UserServiceImpl()) ,它将原实现类 new UserServiceImpl() 通过 bind 方法进行包装成一个proxy代理类,然后用接口类实例 UserService userService 接收。

问题来了: 那为什么不用原来的实现类接收,而是用接口类接收呢??

: proxy相当于这个接口的实现类,而原目标类也是这个接口的实现类。
虽然proxy是增强了目标类,但是
和目标类本质上不一样* !所以相当于现在是有两个实现类,都实现了同一个接口,所以才用接口接收,以免报错。*


3. 动态代理 之 CGLIB代理

定义接口
  • CGLIB 其实不强依赖接口。
java 复制代码
// 不需要接口哦
普通类实现
  • CGLIB 的强大之处在于,即使只有一个普通的类,没有接口,它也能进行代理
java 复制代码
// 普通类 UserServiceImpl (不需要 implements UserService 去实现接口)
public class UserServiceImpl { 
    public void saveUser(String username) { 
        System.out.println("真实业务...正在保存用户: " + username); 
    } 
}
动态代理(重点)
java 复制代码
// 实现动态代理
public class CglibProxyHandler implements MethodInterceptor { 
    // 创建代理对象的核心方法
    public Object bind(Class<?> targetClass) { 
        // 1. 创建增强器(代理工厂)
        Enhancer enhancer = new Enhancer(); 
        // 2. 设置目标类(也就是父类)
        enhancer.setSuperclass(targetClass); 
        // 3. 设置回调(也就是拦截器本身)
        enhancer.setCallback(this); 
        // 4. 创建并返回代理对象
        return enhancer.create(); 
    } 
    @Override 
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 
        // 前置增强 
        System.out.println("CGLIB代理-前置方法调用: " + method.getName());
        // 通过 MethodProxy 调用父类(目标类)的方法
        // 注意:这里使用 invokeSuper 而不是 method.invoke,效率更高
        Object result = methodProxy.invokeSuper(proxy, args); 
        // 后置增强 
        System.out.println("CGLIB代理-后置方法调用");
        return result; 
    } 
}
  • 关于 enhancer :这个所谓的 "增强器" 其实就是一个 "代理工厂 ",可以用 enhancer.create() 生成代理对象 proxy 。
  • 关于 enhancer.setCallback() 方法,参数为 this 。这个方法叫做 "回调 " ,是为了将下面的 intercept 增强的方法传递给代理工厂 enhancer ,等到需要生成代理对象 proxy 的时候,工厂会自动将这个 intercept 增强方法塞进代理对象 proxy 中
  • 对于方法 enhancer.create():这里的 proxy 是一种 "伪造的子类 " ,即将目标类作为父类,让代理类 proxy 去继承目标类 。CGLIB 会在内存中动态生成了一个 继承自目标类 的新类( 就是这个 proxy )。它通过重写父类的所有非 final 方法,并在方法中织入我们的增强逻辑,从而变成了原方法的"替身"。
  • 对于方法 intercept(),这是 CGLIB 的拦截核心。每当调用代理对象的方法时,会执行此方法。
    • proxy:指的是 CGLIB 生成的代理对象本身
    • method:指的是 目标类的方法
    • args:指的是 方法参数
    • methodProxy:这是 CGLIB 特有的参数,它是对目标方法的快速代理,比 JDK 的反射调用性能更好。
实际使用
java 复制代码
// 实际的使用
public class CglibProxyTest {
    public static void main(String[] args) { 
        CglibProxyHandler cglibProxyHandler = new CglibProxyHandler();
        // 用原来的类去接收这个 proxy 代理类
        UserServiceImpl userService = (UserServiceImpl) cglibProxyHandler.bind(UserServiceImpl.class);
        userService.saveUser("张三"); 
    } 
}
  • 对于代码 UserServiceImpl userService = (UserServiceImpl) cglibProxyHandler.bind(UserServiceImpl.class),它将原类 UserServiceImpl.class 通过 bind 方法包装成一个 proxy 代理类,然后用 原类实例 UserServiceImpl userService 接收。
    问题来了:为什么 CGLIB 可以用原来的类接收,而 JDK 代理不行呢??

:因为 CGLIB 采用的是继承 的方式。

CGLIB 生成的代理类是目标类的子类,而目标类是父类

在 Java 中,子类对象是可以赋值给父类引用的(多态)。所以,代理对象(子类)可以直接被当作目标类(父类)来使用,自然就可以用原来的类去接收了。


关于动态代理使用

在 Spring 框架中,AOP 代理的创建是由 AopProxyFactory 决定的。

Spring 默认遵循以下策略:

  • 如果目标对象实现了接口:默认使用 JDK 动态代理。

    建议:在这种情况下,业务类应当实现接口,这是 Java 设计模式的最佳实践。

  • 如果目标对象没有实现接口:必须使用 CGLIB。

  • 强制使用 CGLIB:

    即使目标对象实现了接口,你也可以通过配置强制 Spring 使用 CGLIB ,例如:

    • 在 XML 中设置 <aop:config proxy-target-class="true"/>
    • 在注解配置中开启 proxyTargetClass=true

注意:使用 CGLIB 时,目标类不能是 final 的,且需要被代理的方法也不能是 final 或 private 的(因为子类无法重写这些方法)。


结语

本文结束
如果这篇文章帮你理清了思路,我也感到很开心。
这也同时是我的学习笔记,能帮到别人我不胜感谢,若哪里出错希望指出,同样不胜感激!

以上,@Aroaku

相关推荐
心勤则明3 小时前
Spring AI Alibaba MCP Gateway:将存量服务转换成 MCP Server
人工智能·spring·gateway
鬼先生_sir6 小时前
Spring AI 1.1.4 项目源码深度解析
spring·rag·spingai
pupudawang7 小时前
使用 Logback 的最佳实践:`logback.xml` 与 `logback-spring.xml` 的区别与用法
xml·spring·logback
是Smoky呢7 小时前
springAI+向量数据库(SimpleVectorStore)+RAG深度理解
spring·aigc·ai编程
常利兵8 小时前
Spring Boot 牵手Spring AI,玩转DeepSeek大模型
人工智能·spring boot·spring
星如雨グッ!(๑•̀ㅂ•́)و✧8 小时前
Spring WebFlux中DataBufferLimitException异常的解决方案
java·后端·spring
心勤则明9 小时前
使用 Spring AI Alibaba MCP 结合 Nacos 实现企业级智能体应用
java·人工智能·spring
blxr_9 小时前
Spring AI
数据库·人工智能·spring