代理模式
问题
什么是代理模式?JDK 动态代理和 CGLIB 代理有什么区别?Spring AOP 用的哪种?
答案
核心思想
为目标对象提供一个代理对象,代理对象控制对目标对象的访问,可以在不修改目标对象的前提下增强其功能。
三种代理方式对比
| 方式 | 实现原理 | 要求 | 性能 |
|---|---|---|---|
| 静态代理 | 手写代理类 | 每个接口写一个代理类 | 最快 |
| JDK 动态代理 | Proxy.newProxyInstance() + 反射 | 目标类必须实现接口 | 中等 |
| CGLIB | 生成目标类的子类(字节码增强) | 目标类不能是 final | 较快 |
JDK 动态代理
目标类必须实现接口,代理通过接口反射调用。
JDK 动态代理
public interface UserService {
void save(String name);
}
public class UserServiceImpl implements UserService {
@Override
public void save(String name) {
System.out.println("保存用户: " + name);
}
}
// 代理处理器
public class LogInvocationHandler implements InvocationHandler {
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用前: " + method.getName());
Object result = method.invoke(target, args); // 反射调用目标方法
System.out.println("调用后: " + method.getName());
return result;
}
}
// 创建代理
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class}, // 必须传接口
new LogInvocationHandler(new UserServiceImpl())
);
proxy.save("张三");
CGLIB 代理
通过生成目标类的子类实现代理,不需要接口。
CGLIB 代理
public class OrderService { // 没有实现接口
public void create() {
System.out.println("创建订单");
}
}
public class LogMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("调用前: " + method.getName());
// invokeSuper 调用父类(目标类)的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("调用后: " + method.getName());
return result;
}
}
// 创建代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class);
enhancer.setCallback(new LogMethodInterceptor());
OrderService proxy = (OrderService) enhancer.create();
proxy.create();
Spring AOP 的代理选择
Spring Boot 2.x+ 默认行为
Spring Boot 2.x 开始默认 proxyTargetClass=true,即统一使用 CGLIB,不管目标类是否实现接口。可通过 spring.aop.proxy-target-class=false 改回 JDK 代理。
常见面试问题
Q1: JDK 动态代理为什么必须实现接口?
答案:
JDK 动态代理生成的代理类继承了 java.lang.reflect.Proxy,Java 是单继承的,所以只能通过实现接口来代理。生成的代理类大致是:
public class $Proxy0 extends Proxy implements UserService {
// 所有方法都委托给 InvocationHandler.invoke()
}
Q2: CGLIB 为什么不能代理 final 类/方法?
答案:
CGLIB 通过生成目标类的子类来实现代理,子类重写父类方法插入增强逻辑。final 类不能被继承,final 方法不能被重写,所以无法代理。
Q3: 代理模式在 Spring 中有哪些应用?
答案:
| 应用 | 说明 |
|---|---|
| AOP | 事务、日志、权限等切面通过代理实现 |
| @Transactional | 通过代理对象管理事务的开启和提交 |
| @Async | 通过代理对象实现异步调用 |
| @Cacheable | 通过代理对象拦截方法,先查缓存 |
| Feign | 基于 JDK 动态代理生成 HTTP 客户端 |
| MyBatis Mapper | 接口没有实现类,通过 JDK 动态代理生成 |
Q4: 为什么 Spring 同类方法调用 @Transactional 会失效?
答案:
@Transactional 是通过代理对象拦截方法调用来管理事务的。同类内部调用(this.method())绕过了代理对象,直接调用的是目标对象的方法,所以事务不生效。
详见 Spring 事务管理。