极高 进阶
代理模式#
一句话答案#
代理模式为目标对象提供替身控制访问,JDK 动态代理基于接口,CGLIB 基于继承生成子类,Spring AOP 的核心。
核心要点
代理模式为目标对象提供一个替身,以控制对目标对象的访问。Java 中有三种实现方式:
1. 静态代理
手动编写代理类,实现与目标类相同的接口:
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("提交事务...");
}
}java- 缺点:每个接口都要写一个代理类,代码膨胀
2. JDK 动态代理(基于接口)
运行时通过反射生成代理类,目标类必须实现接口:
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 方法java3. CGLIB 动态代理(基于继承)
通过字节码技术生成目标类的子类作为代理,不要求目标类实现接口:
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();
}
}java三种方式对比:
| 维度 | 静态代理 | 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)
→ 详见 Spring-AOP原理
面试回答(2分钟版)
代理模式的核心是为目标对象提供一个替身来控制访问,在不修改原始代码的前提下做增强。Java中有三种实现:静态代理手动编写代理类实现相同接口,简单但每个接口都要写一个代理类,代码膨胀严重。JDK动态代理通过Proxy.newProxyInstance在运行时生成代理类,基于InvocationHandler的invoke方法做增强,但要求目标类必须实现接口,因为生成的代理类已经继承了Proxy类,Java单继承限制决定了只能通过接口来代理。CGLIB通过ASM字节码技术生成目标类的子类做代理,不要求接口,但不能代理final类和final方法因为子类无法重写。Spring AOP的代理选择逻辑是有接口默认用JDK代理,没接口用CGLIB,不过Spring Boot 2.x之后默认统一用CGLIB,可以通过spring.aop.proxy-target-class配置。日常用到的@Transactional、@Async、@Cacheable底层都是代理模式在发挥作用。
追问与易错
追问方向:
- “JDK 动态代理为什么必须有接口?”→ 生成的代理类已经继承了 java.lang.reflect.Proxy,Java 单继承限制决定了只能通过实现接口来代理目标类的方法
- “CGLIB 为什么不能代理 final 方法?”→ CGLIB 通过 ASM 字节码生成目标类的子类来实现代理,final 方法不可被子类重写,因此无法织入增强逻辑
- “Spring AOP 默认用哪种代理?”→ 有接口默认 JDK 动态代理,无接口用 CGLIB;但 Spring Boot 2.x 起默认 proxy-target-class=true 统一使用 CGLIB
易错点:
- ❌ “代理模式和装饰器模式一样”——代理控制访问,装饰器增强功能
- ❌ “JDK 代理比 CGLIB 慢”——JDK8+ 两者性能差距很小