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

相关推荐
zhenxin012221 小时前
Spring Boot 3.x 系列【3】Spring Initializr快速创建Spring Boot项目
spring boot·后端·spring
oyzz1201 天前
Spring EL 表达式的简单介绍和使用
java·后端·spring
后置的猿猴1 天前
Spring 循环依赖
java·后端·spring
热爱Java,热爱生活1 天前
浅谈Spring三级缓存
java·spring·缓存
shark22222221 天前
Spring 的三种注入方式?
java·数据库·spring
hERS EOUS1 天前
Spring Boot + Spring AI快速体验
人工智能·spring boot·spring
JAVA学习通1 天前
LangChain4j 与 Spring AI 的技术选型深度对比:2026 年 Java AI 工程化实践指南
java·人工智能·spring
yaodong5181 天前
Spring 中使用Mybatis,超详细
spring·tomcat·mybatis
splage1 天前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
zuowei28891 天前
spring实例化对象的几种方式(使用XML配置文件)
xml·java·spring