代理模式
代理模式是一种设计模式,通过代理对象访问真实对象,在不修改原目标对象的前提下,提供额外的功能操作。
提示
代理模式是一种设计模式,通过代理对象访问真实对象,在不修改原目标对象的前提下,提供额外的功能操作
代理模式类型
| 类型 | 实现方式 | 灵活性 | 接口依赖 |
|---|---|---|---|
| 静态代理 | 编译时生成 .class | 低 | 必须实现接口 |
| 动态代理 | 运行时生成字节码 | 高 | JDK代理需接口,CGLIB不需 |
静态代理
静态代理在编译时就已经确定代理类和目标类的关系。
实现步骤
- 定义接口及其实现类
- 创建代理类实现同一接口
- 注入目标对象,调用目标方法
// 接口
public interface SmsService {
String send(String message);
}
// 目标类
public class SmsServiceImpl implements SmsService {
@Override
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
// 代理类
public class SmsProxy implements SmsService {
private final SmsService smsService;
public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}
@Override
public String send(String message) {
System.out.println("before method send()");
String result = smsService.send(message);
System.out.println("after method send()");
return result;
}
}
// 使用
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("java");
}
}静态代理特点
- 缺点:需要为每个目标类编写代理类,代码量大,维护成本高
- 优点:实现简单,逻辑直观
动态代理
动态代理在运行时动态生成代理类字节码,无需手动编写代理类。
JDK 动态代理
JDK 动态代理通过 Proxy.newProxyInstance() 生成代理对象,核心接口是 InvocationHandler。
// 实现 InvocationHandler
public class DebugInvocationHandler implements InvocationHandler {
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}
// 代理工厂
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new DebugInvocationHandler(target)
);
}
}
// 使用
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");JDK 动态代理要求目标类必须实现接口。
CGLIB 动态代理
CGLIB 通过继承方式生成子类作为代理,无需接口依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>// 实现 MethodInterceptor
public class DebugMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
System.out.println("after method " + method.getName());
return object;
}
}
// 代理工厂
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(clazz.getClassLoader());
enhancer.setSuperclass(clazz);
enhancer.setCallback(new DebugMethodInterceptor());
return enhancer.create();
}
}
// 使用
AliSnsService aliSnsService = (AliSnsService) CglibProxyFactory.getProxy(AliSnsService.class);
aliSnsService.send("java");CGLIB 无法代理 final 类和 final/private 方法。
JDK 代理 vs CGLIB 代理
| 对比项 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现原理 | 实现接口,生成实现类 | 继承父类生成子类 |
| 接口要求 | 必须实现接口 | 不需要接口 |
| 代理范围 | 只能代理接口方法 | 可代理所有方法 |
| final 处理 | 代理 final 方法 | 无法代理 final 类/方法 |
| 性能 | JDK 8 后优化明显 | 早期性能更好 |
动态代理原理
两者都是运行时生成字节码,区别在于生成的代理对象形态:
- JDK 代理:
Proxy.newProxyInstance()在运行时生成一个实现目标接口的新类,将方法调用转发给InvocationHandler.invoke()。代理对象与目标类是「兄弟关系」(都实现同一接口),所以目标类必须实现接口。 - CGLIB:
Enhancer在运行时生成目标类的子类,重写非 final 方法,将调用委托给MethodInterceptor。代理对象是目标类的「子孙」,因此目标类不能是 final,final/private 方法也无法被拦截。
两者的拦截本质都是:方法调用 → 拦截器(增强逻辑)→ 反射/方法回调调用真实方法。
Spring AOP 与代理
Spring AOP 的切面功能(@Transactional、@Cacheable、自定义 @Aspect)全部基于动态代理实现。Spring Boot 的代理选择策略:
- Spring Boot 2.x/3.x 默认
spring.aop.proxy-target-class=true,即只要目标类可被继承就强制 CGLIB,避免「接口方法注解不生效」这类问题 - Spring Framework 6 / Boot 3 起,没有接口的类只能用 CGLIB;同时 Spring 官方已提供 CGLIB 重打包版本(
spring-core内置),无需额外引入依赖 - 注意陷阱:JDK 代理下,
this调用(同类内部方法互调)不会经过代理,@Transactional失效;CGLIB 下同类调用同样失效。要自调用生效需注入自身代理或拆分类
应用场景
| 框架 | 代理方式 | 用途 |
|---|---|---|
| Spring AOP | JDK + CGLIB | 切面编程 |
| Spring事务 | JDK | 事务管理 |
| MyBatis | CGLIB | 懒加载 |
| RPC (Dubbo) | JDK | 远程调用 |
| Spring DI | CGLIB | Bean 创建 |
Spring 中,如果目标对象实现了接口,默认使用 JDK 动态代理;否则使用 CGLIB。
FAQ
Q: JDK 动态代理和 CGLIB 如何选择? A: 目标类实现了接口可以用 JDK 动态代理,没有接口只能用 CGLIB。Spring Boot 2.x/3.x 默认 proxyTargetClass=true 强制走 CGLIB(只要类可继承),JDK 8 之后两者的性能差距已经很小。手动选择时遵循「目标有接口且重视接口编程 → JDK;目标无接口或需要代理 final 场景 → CGLIB」。注意:@Transactional 等注解写在接口方法上时,JDK 代理无法识别,需写在实现类方法上。
Q: 代理模式和装饰器模式有什么区别? A: 代理模式控制对目标对象的访问(如权限校验、懒加载),目标对象通常不直接暴露给客户端;装饰器模式增强目标对象的功能(如添加日志、缓存),客户端直接使用装饰后的对象。代理关注"能不能访问",装饰器关注"怎么增强"。
Q: Spring AOP 的代理是如何创建的? A: Spring 容器启动时,对需要 AOP 增强的 Bean 创建代理对象。如果 Bean 实现了接口,默认使用 JDK 动态代理;否则使用 CGLIB。代理对象拦截方法调用,执行切面逻辑(前置通知、后置通知、环绕通知等),再调用目标方法。