外观
一句话答案
代理模式为目标对象提供替身控制访问,JDK 动态代理基于接口,CGLIB 基于继承生成子类,Spring AOP 的核心。
核心要点
代理模式为目标对象提供一个替身,以控制对目标对象的访问。Java 中有三种实现方式:
1. 静态代理
手动编写代理类,实现与目标类相同的接口:
java
public interface UserService {
void save(User user);
}
public class UserServiceImpl implements UserService {
public void save(User user) { /* 保存用户 */ }
}
// 静态代理类
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
public void save(User user) {
System.out.println("开始事务...");
target.save(user); // 委托给目标对象
System.out.println("提交事务...");
}
}- 缺点:每个接口都要写一个代理类,代码膨胀
2. JDK 动态代理(基于接口)
运行时通过反射生成代理类,目标类必须实现接口:
java
public class JdkProxyHandler implements InvocationHandler {
private Object target;
public JdkProxyHandler(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;
}
// 获取代理对象
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(), // 必须有接口
new JdkProxyHandler(target)
);
}
}
// 使用
UserService proxy = JdkProxyHandler.createProxy(new UserServiceImpl());
proxy.save(user); // 调用时会经过 invoke 方法3. CGLIB 动态代理(基于继承)
通过字节码技术生成目标类的子类作为代理,不要求目标类实现接口:
java
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("前置增强");
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("后置增强");
return result;
}
public static <T> T createProxy(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz); // 设置父类
enhancer.setCallback(new CglibProxy());
return (T) enhancer.create();
}
}三种方式对比:
| 维度 | 静态代理 | JDK 动态代理 | CGLIB |
|---|---|---|---|
| 实现方式 | 手写代理类 | Proxy + InvocationHandler | 字节码生成子类 |
| 要求 | 实现相同接口 | 目标类必须有接口 | 目标类不能是 final |
| 性能 | 编译期确定,最快 | 反射调用,较慢 | 字节码直接调用,略快于 JDK |
| Spring 选择 | — | 有接口时默认使用 | 无接口时使用(Spring Boot 2.x+ 默认 CGLIB) |
Spring AOP 的代理选择逻辑:
- 目标类实现了接口 → 默认 JDK 动态代理
- 目标类没有实现接口 → 使用 CGLIB
- Spring Boot 2.x 之后默认都使用 CGLIB(
spring.aop.proxy-target-class=true)
→ 详见 Module 07 Spring AOP 部分
追问与易错
追问方向:
- JDK 动态代理为什么必须有接口?(Proxy.newProxyInstance 需要接口)
- CGLIB 为什么不能代理 final 方法?(CGLIB 通过继承生成子类,final 不可重写)
- Spring AOP 默认用哪种代理?(有接口用 JDK,无接口用 CGLIB;Boot 2.x 默认 CGLIB)
易错点:
- ❌ "代理模式和装饰器模式一样"——代理控制访问,装饰器增强功能
- ❌ "JDK 代理比 CGLIB 慢"——JDK8+ 两者性能差距很小
💡 记忆锚点
代理是明星的经纪人:静态代理是一个明星配一个经纪人(太累),JDK动态代理是经纪公司按合同(接口)派人,CGLIB是直接派个替身演员(继承子类)。Spring AOP整套事务/缓存/异步都靠经纪人干活。