跳到主要内容

代理模式

问题

什么是代理模式?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 事务管理

相关链接