# 代理模式:隐藏在框架幕后的核心模式
# 一、模式本质与核心价值
代理模式(Proxy Pattern)是一种结构型设计模式,通过创建代理对象控制对原始对象的访问。在 Java 生态中,代理模式是实现以下能力的基石:
- 访问控制:权限校验、熔断限流
- 功能增强:日志记录、性能监控
- 延迟加载:大文件预加载、数据库连接池
- 远程调用:RPC 框架通信基础
# 二、Java 实现方式对比
# 2.1 静态代理(显式代理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| interface UserService { void saveUser(String user); }
class UserServiceImpl implements UserService { public void saveUser(String user) { System.out.println("保存用户:" + user); } }
class UserServiceProxy implements UserService { private UserService target; public UserServiceProxy(UserService target) { this.target = target; } public void saveUser(String user) { long start = System.currentTimeMillis(); target.saveUser(user); System.out.println("方法耗时:" + (System.currentTimeMillis()-start)); } }
UserService service = new UserServiceProxy(new UserServiceImpl()); service.saveUser("张三");
|
特点:
- 手动编写代理类
- 接口方法变化时代码需要同步修改
- 适合小型固定场景
# 2.2 JDK 动态代理(接口代理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class LogInvocationHandler implements InvocationHandler { private final Object target; public LogInvocationHandler(Object target) { this.target = target; } 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; } }
UserService proxyInstance = (UserService) Proxy.newProxyInstance( UserService.class.getClassLoader(), new Class[]{UserService.class}, new LogInvocationHandler(new UserServiceImpl()) );
|
特点:
# 2.3 CGLIB 动态代理(子类代理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class CglibProxyInterceptor implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("CGLIB前置处理"); Object result = proxy.invokeSuper(obj, args); System.out.println("CGLIB后置处理"); return result; } }
Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserServiceImpl.class); enhancer.setCallback(new CglibProxyInterceptor()); UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
|
特点:
- 通过生成子类实现
- 不需要接口
- 需要添加 CGLIB 依赖
- final 类 / 方法无法代理
# 三、Spring 生态中的代理应用场景
# 3.1 Spring AOP 实现原理
Spring 框架通过代理模式实现 AOP,两种代理选择策略:
| 条件 | 使用代理类型 |
|---|
| 目标实现接口 | JDK 动态代理 |
| 目标未实现接口 | CGLIB 代理 |
| 强制使用 CGLIB | 配置 @EnableAspectJAutoProxy (proxyTargetClass=true) |
事务管理的典型实现:
1 2 3 4 5
| @Transactional public void transferMoney() { }
|
# 3.2 Spring Boot 自动配置
在 Spring Boot 启动过程中,Configuration 类通过代理实现:
1 2 3 4 5 6 7 8
| @Configuration public class AppConfig { @Bean public DataSource dataSource() { } }
|
# 3.3 Feign 声明式 HTTP 客户端
OpenFeign 通过动态代理生成 HTTP 客户端:
1 2 3 4 5 6
| @FeignClient(name = "user-service") public interface UserClient { @GetMapping("/users/{id}") User getUser(@PathVariable Long id); }
|
# 3.4 MyBatis Mapper 接口实现
MyBatis 通过 JDK 动态代理将接口转换为 SQL 执行:
1 2 3 4 5
| public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{id}") User selectUser(int id); }
|
# 四、代理模式高级应用场景
# 4.1 分布式追踪(APM 工具)
1 2 3 4 5 6 7 8 9 10 11
| public class TraceProxy implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) { Span span = Tracer.startSpan(method.getName()); try { return method.invoke(target, args); } finally { span.finish(); } } }
|
# 4.2 微服务熔断器
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class CircuitBreakerProxy implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) { if (circuit.isOpen()) { return fallback(); } try { return method.invoke(target, args); } catch (Exception e) { circuit.recordFailure(); throw e; } } }
|
# 五、性能优化建议
- 代理对象缓存:对相同目标重复使用代理实例
- 方法过滤:仅代理需要增强的方法
- 选择合适代理类型:
- 高频率调用方法避免反射
- 优先选择 CGLIB(Spring Boot 2.x 默认)
- 减少代理层级:避免多重代理嵌套
# 六、代理模式局限性
- 调试复杂性:调用栈深度增加
- 性能损耗:动态代理比直接调用慢 3-5 倍(实测数据)
- 对象身份变化:
instanceof 、 equals() 等方法需要特殊处理 - 循环依赖:在 Spring 中需要特殊处理代理对象的依赖注入
# 七、一些特殊处理:
在代理模式中,由于代理对象和真实对象是两个不同的 Java 对象实例,它们在以下两种场景中会出现特殊行为,需要特别注意处理
# 一、 instanceof 判断失效
问题本质:
代理对象(Proxy)和真实对象(Target)属于不同的类层次结构。
示例场景:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface UserService { void save(); }
public class UserServiceImpl implements UserService { public void save() { } }
UserService proxy = (UserService) Proxy.newProxyInstance(...);
boolean isInstance = proxy instanceof UserServiceImpl;
|
原因分析:
- JDK 动态代理生成的是
Proxy 子类(如 $Proxy0 ) - CGLIB 生成的是真实对象的子类(如
UserServiceImpl$$EnhancerByCGLIB$$123456 ) - 两者都与原始类存在类型差异
解决方案:
1 2 3 4 5 6 7 8
| if(AopUtils.isAopProxy(proxy)) { Object target = AopUtils.getTargetObject(proxy); if(target instanceof UserServiceImpl) { } }
|
# 二、 equals() 方法陷阱
问题本质:
默认的 equals() 方法基于对象内存地址比较。
* 示例场景
1 2 3 4 5
| UserService real = new UserServiceImpl(); UserService proxy = createProxy(real);
boolean equals = proxy.equals(real);
|
危险后果:
- 将代理对象放入 HashSet/HashMap 时可能出现重复
- 使用对象相等判断的逻辑可能失效
解决方案:
- 重写 equals 方法(在真实对象中)
1 2 3 4 5 6 7 8
| @Override public boolean equals(Object obj) { if (obj instanceof UserServiceImpl) { return this.id == ((UserServiceImpl)obj).id; } return false; }
|
- 代理对象特殊处理(Spring 的 AOP 代理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class MyProxy extends Proxy implements Advised { public Object getTarget() { return targetSource.getTarget(); }
@Override public boolean equals(Object other) { if (other == this) return true; if (other instanceof MyProxy) { return this.getTarget().equals(((MyProxy)other).getTarget()); } return this.getTarget().equals(other); } }
|
# 三、框架中的特殊处理(以 Spring 为例)
# 1. @Autowired 注入处理
当注入被代理对象时,Spring 会自动注入代理对象:
1 2 3 4 5 6 7 8
| @Autowired private UserService userService;
if(AopProxyUtils.getSingletonTarget(userService) != null) { Object target = AopProxyUtils.getSingletonTarget(userService); }
|
# 2. 事务传播中的自调用问题
1 2 3 4 5 6 7 8 9 10 11 12
| public class UserService { @Transactional public void methodA() { this.methodB(); }
@Transactional public void methodB() { } }
((UserService) AopContext.currentProxy()).methodB();
|
# 四、最佳实践总结
避免直接类型判断
使用 AopUtils.isAopProxy() 替代 instanceof
慎用对象级 equals()
始终基于业务属性实现相等性判断
注意集合操作
代理对象存入集合前需做归一化处理:
1 2
| Set<UserService> set = new HashSet<>(); set.add(AopUtils.getTargetObject(proxy));
|
框架特性利用
Spring 提供的工具类:
1 2
| AopProxyUtils.getSingletonTarget(proxy) AopContext.currentProxy()
|