代理模式
代理模式是一种设计模式,通过代理对象访问真实对象,在不修改原目标对象的前提下,提供额外的功能操作。
提示
代理模式是一种设计模式,通过代理对象访问真实对象,在不修改原目标对象的前提下,提供额外的功能操作
代理模式类型
| 类型 | 实现方式 | 灵活性 | 接口依赖 |
|---|---|---|---|
| 静态代理 | 编译时生成 .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()");
smsService.send(message);
System.out.println("after method send()");
return null;
}
}
// 使用
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 后优化明显 | 早期性能更好 |
应用场景
| 框架 | 代理方式 | 用途 |
|---|---|---|
| Spring AOP | JDK + CGLIB | 切面编程 |
| Spring事务 | JDK | 事务管理 |
| MyBatis | CGLIB | 懒加载 |
| RPC (Dubbo) | JDK | 远程调用 |
| Spring DI | CGLIB | Bean 创建 |
Spring 中,如果目标对象实现了接口,默认使用 JDK 动态代理;否则使用 CGLIB。
FAQ
Q: JDK 动态代理和 CGLIB 如何选择? A: 目标类实现了接口用 JDK 动态代理(Spring Boot 2.x 默认),目标类没有接口用 CGLIB。Spring Boot 2.x 已将默认策略改为 CGLIB(proxyTargetClass=true),因为 CGLIB 不要求实现接口、使用更简单。JDK 8 之后两者的性能差距已经很小。
Q: 代理模式和装饰器模式有什么区别? A: 代理模式控制对目标对象的访问(如权限校验、懒加载),目标对象通常不直接暴露给客户端;装饰器模式增强目标对象的功能(如添加日志、缓存),客户端直接使用装饰后的对象。代理关注"能不能访问",装饰器关注"怎么增强"。
Q: Spring AOP 的代理是如何创建的? A: Spring 容器启动时,对需要 AOP 增强的 Bean 创建代理对象。如果 Bean 实现了接口,默认使用 JDK 动态代理;否则使用 CGLIB。代理对象拦截方法调用,执行切面逻辑(前置通知、后置通知、环绕通知等),再调用目标方法。