只会用Spring AOP写@Transactional、@Async?
其实Spring AOP的底层,全是代理模式的身影。90%的高级开发面试,都会被问:"静态代理和动态代理的区别?JDK代理和CGLIB代理底层实现有何不同?"
代理模式是Java高级开发的"基本功",更是Spring AOP、事务控制、权限拦截、日志监控的核心底层。不懂代理,就看不懂Spring的核心设计,更无法应对复杂业务的扩展与优化。
一、先搞懂:代理模式到底解决什么问题?
在Java开发中,经常会遇到这样的需求:在不修改原有业务代码的前提下,给方法添加额外逻辑------比如日志打印、权限校验、事务控制、性能监控。
如果直接修改业务代码,会违反"开闭原则"(对扩展开放、对修改关闭),而且会让业务代码变得臃肿,日志、权限等非业务逻辑与核心业务耦合在一起,后续维护成本极高。
代理模式的核心价值,就是「解耦」:通过一个"代理对象",包装原始的"目标对象",在不修改目标对象源码的前提下,对目标方法进行增强、拦截和扩展。
核心角色
-
目标对象(Target):被增强的原始业务类,负责执行核心业务逻辑;
-
代理对象(Proxy):包装目标对象,负责添加额外逻辑(增强),并调用目标对象的核心方法;
-
公共接口(Interface):目标对象和代理对象共同实现的接口(静态代理、JDK动态代理必需),保证代理对象和目标对象对外的行为一致,客户端无需感知代理的存在。
举个通俗的例子:你去房产中介买房,你(客户端)不需要直接和房东(目标对象)打交道,中介(代理对象)会帮你筛选房源、谈价格、办手续(增强逻辑),最终还是房东把房子卖给你(核心业务)------中介就是"代理",帮你做了额外的事情,却没有改变"买房"这个核心业务本身。
二、静态代理:手写代理类,简单但笨重
静态代理,顾名思义,就是「提前手写好代理类」,代理类和目标类一一对应,在程序编译时就已经确定好代理关系,无法动态修改。
1. 静态代理完整实战(以"用户管理"为例)
需求:给用户新增、删除的业务方法,添加"日志打印"和"权限校验"的增强逻辑,不修改原有业务代码。
步骤1:定义公共业务接口(规范目标对象和代理对象的行为)
java
/**
* 公共接口:用户服务
* 目标对象和代理对象都必须实现此接口
*/
public interface UserService {
// 新增用户
void addUser(String username);
// 删除用户
void deleteUser(Long userId);
}
步骤2:实现目标对象(核心业务逻辑)
java
/**
* 目标对象:用户服务实现类(核心业务)
* 只关注业务逻辑,不掺杂任何非业务代码(日志、权限等)
*/
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
// 核心业务:新增用户
System.out.println("核心业务:新增用户【" + username + "】");
}
@Override
public void deleteUser(Long userId) {
// 核心业务:删除用户
System.out.println("核心业务:删除用户【" + userId + "】");
}
}
步骤3:手写静态代理类(实现接口,组合目标对象)
java
/**
* 静态代理类:用户服务代理
* 实现和目标对象相同的接口,组合目标对象,添加增强逻辑
*/
public class UserStaticProxy implements UserService {
// 组合目标对象(核心:代理对象不直接实现业务,而是调用目标对象的业务方法)
private final UserService target;
// 构造方法注入目标对象
public UserStaticProxy(UserService target) {
this.target = target;
}
@Override
public void addUser(String username) {
// 增强逻辑1:权限校验(前置增强)
System.out.println("静态代理:权限校验通过,允许执行新增用户操作");
// 增强逻辑2:日志打印(前置增强)
System.out.println("静态代理:开始执行新增用户方法,参数:" + username);
// 调用目标对象的核心业务方法
target.addUser(username);
// 增强逻辑3:日志打印(后置增强)
System.out.println("静态代理:新增用户方法执行完成");
}
@Override
public void deleteUser(Long userId) {
// 前置增强:权限校验+日志
System.out.println("静态代理:权限校验通过,允许执行删除用户操作");
System.out.println("静态代理:开始执行删除用户方法,参数:" + userId);
// 执行核心业务
target.deleteUser(userId);
// 后置增强:日志
System.out.println("静态代理:删除用户方法执行完成");
}
}
步骤4:客户端调用(无感知代理)
java
/**
* 测试类:客户端调用
* 客户端只和代理对象交互,不知道目标对象的存在
*/
public class StaticProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象(核心业务类)
UserService target = new UserServiceImpl();
// 2. 创建代理对象,注入目标对象
UserService proxy = new UserStaticProxy(target);
// 3. 调用代理对象的方法(间接调用目标对象的方法,同时执行增强逻辑)
proxy.addUser("Java高级开发");
System.out.println("------------------------");
proxy.deleteUser(1L);
}
}
运行结果
text
静态代理:权限校验通过,允许执行新增用户操作
静态代理:开始执行新增用户方法,参数:Java高级开发
核心业务:新增用户【Java高级开发】
静态代理:新增用户方法执行完成
------------------------
静态代理:权限校验通过,允许执行删除用户操作
静态代理:开始执行删除用户方法,参数:1
核心业务:删除用户【1】
静态代理:删除用户方法执行完成
2. 静态代理的优缺点
✅ 优点
-
实现简单、逻辑清晰,无需依赖任何框架;
-
运行效率高,编译时就确定代理关系,无反射、无动态生成开销;
-
增强逻辑明确,可精准控制每个方法的增强细节。
❌ 缺点(致命缺陷,限制其使用场景)
-
类爆炸问题:一个目标类对应一个代理类,如果有100个业务类,就需要写100个代理类,维护成本极高;
-
硬编码冗余:多个代理类的增强逻辑(如日志、权限)重复,无法复用;
-
扩展性极差:如果接口新增一个方法,所有实现该接口的目标类和代理类都必须同步修改,违反开闭原则。
正因为这些缺陷,静态代理只适用于「简单场景、少量类」的增强,在实际企业开发中(尤其是微服务场景),几乎不会使用静态代理------此时,动态代理应运而生。
三、动态代理:运行时自动生成代理类,优雅且灵活
动态代理的核心的是:不需要手写代理类,程序运行期间,通过反射、字节码生成等技术,动态创建代理对象。
也就是说,代理类不是提前写好的,而是程序运行时"临时生成"的,一个代理处理器可以适配所有目标类,彻底解决静态代理的类爆炸、冗余问题。
Java 中主流的两种动态代理方式:
-
JDK 动态代理:基于「接口」实现,JDK 原生支持,无需引入额外依赖;
-
CGLIB 动态代理:基于「子类继承」实现,需要引入第三方依赖,可代理无接口的普通类。
四、JDK 动态代理(Spring 默认首选)
JDK 动态代理是 Java 原生提供的代理方式,底层依赖java\.lang\.reflect\.Proxy 类和 InvocationHandler接口,核心要求:目标类必须实现接口。
核心原理
-
客户端调用代理对象的方法时,会被
InvocationHandler的invoke方法拦截; -
在
invoke方法中,可以添加增强逻辑(前置、后置); -
通过
Method\.invoke\(target, args\)调用目标对象的核心方法; -
程序运行时,JDK 会自动生成一个实现了目标接口的代理类(字节码),无需手写。
1. JDK 动态代理完整实战
步骤1:复用之前的 UserService 接口和 UserServiceImpl 目标类
步骤2:实现 InvocationHandler(代理处理器,统一处理增强逻辑)
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* JDK动态代理处理器
* 所有的增强逻辑,都在这里统一实现,可适配所有实现接口的目标类
*/
public class JdkProxyHandler implements InvocationHandler {
// 目标对象(通用,可接收任意实现接口的目标类)
private final Object target;
// 构造方法注入目标对象
public JdkProxyHandler(Object target) {
this.target = target;
}
/**
* 代理方法拦截:所有代理对象的方法调用,都会走到这里
* @param proxy 代理对象(自身)
* @param method 被调用的目标方法
* @param args 方法参数
* @return 目标方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强:权限校验 + 日志打印(统一增强,所有方法都生效)
System.out.println("JDK动态代理:权限校验通过");
System.out.println("JDK动态代理:开始执行方法【" + method.getName() + "】,参数:" + args[0]);
// 调用目标对象的核心业务方法
Object result = method.invoke(target, args);
// 后置增强:日志打印
System.out.println("JDK动态代理:方法【" + method.getName() + "】执行完成");
return result;
}
}
步骤3:生成代理对象 + 客户端调用
java
import java.lang.reflect.Proxy;
/**
* JDK动态代理测试
*/
public class JdkProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象
UserService target = new UserServiceImpl();
// 2. 创建代理处理器,注入目标对象
JdkProxyHandler handler = new JdkProxyHandler(target);
// 3. 动态生成代理对象(核心:JDK自动生成代理类)
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器
target.getClass().getInterfaces(), // 目标类实现的所有接口
handler // 代理处理器
);
// 4. 调用代理对象的方法
proxy.addUser("JDK动态代理");
System.out.println("------------------------");
proxy.deleteUser(2L);
}
}
2. JDK 动态代理的优缺点
✅ 优点
-
无需手写代理类,一个处理器适配所有实现接口的目标类,解决类爆炸问题;
-
JDK 原生支持,无需引入第三方依赖,轻量化;
-
增强逻辑统一管理,可复用、可扩展。
❌ 缺点
-
强制依赖接口:目标类必须实现接口,否则无法使用 JDK 动态代理;
-
只能代理接口中的方法,无法代理目标类中的非接口方法(如私有方法、默认方法)。
五、CGLIB 动态代理(无接口场景首选)
CGLIB(Code Generation Library)是一个第三方字节码生成库,它的核心原理是:通过字节码技术,动态生成目标类的子类,子类就是代理类。
正因为是基于继承实现的,所以 CGLIB 不需要目标类实现接口,可直接代理普通类------这也是它弥补 JDK 动态代理缺陷的核心优势。
注意:Spring 中已经集成了 CGLIB,无需额外引入依赖;如果是纯 Java 项目,需要手动引入 CGLIB 依赖。
核心原理
-
通过
Enhancer类(CGLIB 核心类)设置目标类为父类; -
实现
MethodInterceptor接口,重写intercept方法,添加增强逻辑; -
程序运行时,CGLIB 动态生成目标类的子类(代理类),并重写父类的方法;
-
客户端调用代理类的方法时,会被
intercept方法拦截,执行增强逻辑后,调用父类(目标类)的方法。
1. CGLIB 动态代理完整实战
步骤1:定义无接口的目标类(测试 CGLIB 无接口代理能力)
java
/**
* 无接口的目标类:普通业务类
* CGLIB 可直接代理此类,无需实现任何接口
*/
public class OrderService {
// 核心业务:创建订单
public void createOrder(Long orderId) {
System.out.println("核心业务:创建订单【" + orderId + "】");
}
// 核心业务:取消订单
public void cancelOrder(Long orderId) {
System.out.println("核心业务:取消订单【" + orderId + "】");
}
}
步骤2:实现 MethodInterceptor(CGLIB 代理处理器)
java
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB动态代理处理器
* 基于子类继承,无需接口,可代理任意普通类
*/
public class CglibProxyHandler implements MethodInterceptor {
/**
* 生成代理对象
* @param targetClass 目标类的Class对象
* @return 代理对象(目标类的子类)
*/
public <T> T getProxy(Class<T> targetClass) {
// 1. 创建Enhancer对象(CGLIB核心类,用于生成代理类)
Enhancer enhancer = new Enhancer();
// 2. 设置父类(目标类)
enhancer.setSuperclass(targetClass);
// 3. 设置代理处理器(当前类)
enhancer.setCallback(this);
// 4. 动态生成代理类,并返回代理对象
return (T) enhancer.create();
}
/**
* 拦截代理对象的方法调用
* @param obj 代理对象(目标类的子类)
* @param method 被调用的目标方法(父类的方法)
* @param args 方法参数
* @param proxy 方法代理对象(用于调用父类方法)
* @return 目标方法的返回值
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置增强:日志打印
System.out.println("CGLIB动态代理:开始执行方法【" + method.getName() + "】,参数:" + args[0]);
// 调用目标类(父类)的核心方法(注意:用proxy.invokeSuper,不是method.invoke)
Object result = proxy.invokeSuper(obj, args);
// 后置增强:日志打印
System.out.println("CGLIB动态代理:方法【" + method.getName() + "】执行完成");
return result;
}
}
步骤3:生成代理对象 + 客户端调用
java
/**
* CGLIB动态代理测试
*/
public class CglibProxyTest {
public static void main(String[] args) {
// 1. 创建CGLIB代理处理器
CglibProxyHandler handler = new CglibProxyHandler();
// 2. 动态生成代理对象(目标类的子类)
OrderService proxy = handler.getProxy(OrderService.class);
// 3. 调用代理对象的方法
proxy.createOrder(1001L);
System.out.println("------------------------");
proxy.cancelOrder(1001L);
}
}
2. CGLIB 动态代理的优缺点
✅ 优点
-
无需目标类实现接口,可代理任意普通类,适用范围更广;
-
可代理目标类中的所有方法(除了final方法),包括私有方法、默认方法;
-
运行效率略高于 JDK 动态代理(无反射调用,直接调用父类方法)。
❌ 缺点
-
需要引入第三方依赖(Spring 已集成,无需额外处理);
-
基于继承实现,无法代理 final 类和 final 方法(final 类不能被继承,final 方法不能被重写);
-
动态生成子类的字节码,初始化代理对象的开销略大于 JDK 动态代理。
六、三大代理方式核心对比
| 代理类型 | 实现方式 | 依赖条件 | 核心优点 | 核心缺点 | 适用场景 |
|---|---|---|---|---|---|
| 静态代理 | 手写代理类,组合目标对象 | 目标类和代理类实现同一接口 | 实现简单、运行效率高 | 类爆炸、维护成本高、扩展性差 | 简单场景、少量类的增强 |
| JDK 动态代理 | Proxy + InvocationHandler,反射机制 | 目标类必须实现接口 | 无类爆炸、原生支持、轻量化 | 无法代理无接口的类 | 有接口的业务类增强(Spring 默认) |
| CGLIB 动态代理 | Enhancer + MethodInterceptor,字节码生成子类 | 无接口要求,不能是final类/方法 | 适用范围广,可代理普通类 | 依赖第三方,无法代理final | 无接口的业务类增强 |
七、Spring 中的代理模式落地
日常用的 Spring AOP、@Transactional、@Async、统一日志拦截,底层全是动态代理------Spring 会根据目标类是否有接口,自动选择代理方式。
1. Spring 代理选择规则(核心)
-
如果目标类实现了接口 :Spring 默认使用 JDK 动态代理;
-
如果目标类没有实现接口 :Spring 自动切换为 CGLIB 动态代理;
-
可通过配置强制开启 CGLIB 代理(无论是否有接口):
spring: aop: proxy\-target\-class: true \# 强制使用CGLIB代理
2. Spring AOP 代理的常见坑(生产实战必避)
坑1:内部方法调用,AOP 增强失效
场景:在目标类中,一个方法调用自身的另一个被 AOP 增强的方法(如 @Transactional 方法),增强逻辑不生效。
java
@Service
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
// 内部调用:this.deleteUser(1L),不会走代理,@Transactional失效
this.deleteUser(1L);
System.out.println("新增用户:" + username);
}
@Transactional
@Override
public void deleteUser(Long userId) {
System.out.println("删除用户:" + userId);
}
}
原因:this 指向的是目标对象本身,不是代理对象,无法触发 AOP 拦截。
解决方案:通过 AopContext\.currentProxy\(\) 获取代理对象,再调用方法:
java
// 替换 this.deleteUser(1L) 为以下代码
((UserService) AopContext.currentProxy()).deleteUser(1L);
坑2:final 方法无法被 AOP 增强
如果目标方法被 final 修饰,无论用 JDK 还是 CGLIB 代理,增强逻辑都不会生效------CGLIB 无法重写 final 方法,JDK 代理也无法拦截 final 方法。
八、总结:如何用好代理模式?
-
简单场景、少量类增强:用静态代理(虽然笨重,但高效、易懂);
-
企业级开发、大量类增强:用动态代理(优先 JDK 代理,无接口用 CGLIB);
-
Spring 项目:无需手动选择代理方式,Spring 自动适配,重点关注 AOP 内部调用失效的坑;
-
重点:记住三大代理的实现方式、优缺点、依赖条件,以及 Spring 代理选择规则。