1. 代理模式基础概念与核心价值
代理模式(Proxy Pattern)是Java设计模式中最常被问及的结构型模式之一,它通过创建一个代理对象来控制对原始对象的访问。这种控制在分布式系统、AOP编程、安全控制等场景中具有不可替代的价值。
1.1 什么是代理模式
代理模式的本质是"控制访问"。想象你是一个明星的经纪人——所有想联系明星的人必须先通过你,由你决定是否转发请求、何时转发以及如何处理结果。在代码层面,代理类与被代理类实现相同的接口,客户端调用代理对象的方法时,代理对象可以在调用真实对象前后加入自己的逻辑。
java复制public interface Subject {
void request();
}
public class RealSubject implements Subject {
public void request() {
System.out.println("RealSubject handling request");
}
}
public class Proxy implements Subject {
private RealSubject realSubject;
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.request();
postRequest();
}
private void preRequest() { /* ... */ }
private void postRequest() { /* ... */ }
}
1.2 为什么需要代理模式
代理模式的核心价值体现在三个维度:
- 职责清晰:将非核心逻辑(如日志、监控)从业务类中剥离,符合单一职责原则
- 对象保护:通过代理控制对敏感对象的访问,实现权限校验等安全措施
- 性能优化:延迟加载、缓存结果等优化手段可以通过代理透明地实现
在大厂实际项目中,代理模式最常见的应用场景包括:
- Spring AOP的底层实现
- MyBatis的Mapper接口动态代理
- RPC框架的客户端存根生成
- 应用性能监控(APM)系统的埋点采集
提示:面试时被问到"为什么要用代理模式",不要只回答"控制访问",要结合具体业务场景说明其不可替代性。例如:"在我们的订单系统中,通过代理模式实现了下单前的风控校验和下单后的日志记录,这样业务类只需要关注核心下单逻辑"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 静态代理与动态代理深度对比
Java中的代理实现主要分为静态代理和动态代理两大类,它们在实现机制和使用场景上有本质区别。
2.1 静态代理的实现与局限
静态代理是最基础的代理形式,需要手动创建代理类。下面是一个数据库查询的静态代理示例:
java复制public interface DatabaseQuery {
String query(String sql);
}
public class RealDatabaseQuery implements DatabaseQuery {
public String query(String sql) {
// 实际数据库操作
return "Result for: " + sql;
}
}
public class DatabaseQueryProxy implements DatabaseQuery {
private DatabaseQuery realQuery;
private Cache cache;
public DatabaseQueryProxy() {
this.realQuery = new RealDatabaseQuery();
this.cache = new Cache();
}
public String query(String sql) {
// 先查缓存
String cached = cache.get(sql);
if (cached != null) {
return cached;
}
// 执行真实查询
String result = realQuery.query(sql);
// 结果缓存
cache.put(sql, result);
return result;
}
}
静态代理的三大痛点:
- 类爆炸:每个被代理类都需要一个对应的代理类
- 维护困难:接口变更时需要同步修改代理类
- 灵活性差:代理逻辑无法动态变化
2.2 JDK动态代理的实现原理
JDK动态代理基于反射机制,在运行时动态生成代理类。核心类有两个:
java.lang.reflect.Proxy:负责生成代理类java.lang.reflect.InvocationHandler:定义代理逻辑
java复制public class LoggingHandler implements InvocationHandler {
private Object target;
public LoggingHandler(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;
}
}
// 使用示例
DatabaseQuery realQuery = new RealDatabaseQuery();
DatabaseQuery proxy = (DatabaseQuery) Proxy.newProxyInstance(
realQuery.getClass().getClassLoader(),
realQuery.getClass().getInterfaces(),
new LoggingHandler(realQuery)
);
JDK动态代理的关键限制:
- 只能代理接口
- 性能开销:反射调用比直接调用慢约3-5倍
- 无法代理final类和方法
2.3 CGLIB动态代理的底层机制
CGLIB(Code Generation Library)通过字节码增强技术实现代理,不要求目标类实现接口。其核心原理是:
- 生成目标类的子类
- 重写父类方法
- 在子类中插入代理逻辑
java复制public class CglibProxy implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method: " + method.getName());
return result;
}
}
// 使用示例
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealDatabaseQuery.class);
enhancer.setCallback(new CglibProxy());
DatabaseQuery proxy = (DatabaseQuery) enhancer.create();
CGLIB的典型问题:
- final方法限制:无法代理被final修饰的方法
- 构造器调用:代理对象的构造器会执行两次
- 方法拦截:对从代理对象内部调用的方法无效
避坑指南:在Spring AOP中,默认对接口使用JDK动态代理,对类使用CGLIB。如果希望统一使用CGLIB,需要配置
@EnableAspectJAutoProxy(proxyTargetClass = true)
3. 代理模式在大厂项目中的实战应用
3.1 RPC框架中的代理应用
分布式系统中,RPC框架大量使用动态代理技术。以Dubbo为例,其服务引用过程的核心代理逻辑:
java复制public class ReferenceConfig<T> {
// 创建代理对象
public T get() {
// 创建Invoker
Invoker<T> invoker = protocol.refer(interfaceClass, urls.get(0));
// 生成代理
return (T) Proxy.getProxy(interfaceClass).newInstance(new InvokerInvocationHandler(invoker));
}
}
// 调用处理器
public class InvokerInvocationHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 构造RPC请求
RpcInvocation invocation = new RpcInvocation(method, args);
// 网络传输
return invoker.invoke(invocation).recreate();
}
}
关键设计点:
- 将本地方法调用转换为网络请求
- 处理序列化/反序列化
- 负载均衡与容错机制
3.2 MyBatis Mapper接口的代理实现
MyBatis通过JDK动态代理将Mapper接口转换为SQL执行:
java复制public class MapperProxy<T> implements InvocationHandler {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 处理Object方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 转换方法为MapperMethod
MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行SQL
return mapperMethod.execute(sqlSession, args);
}
}
面试常见问题:
- 为什么Mapper接口不需要实现类?
- 方法名如何映射到SQL语句?
- 参数是如何绑定到SQL的?
3.3 Spring AOP的代理选择策略
Spring AOP根据以下规则选择代理方式:
| 条件 | 代理类型 |
|---|---|
| 目标实现接口且proxyTargetClass=false | JDK动态代理 |
| 目标未实现接口或proxyTargetClass=true | CGLIB代理 |
| 目标类是final类 | 抛出异常 |
性能对比(基于Spring 5.3测试):
| 操作 | JDK代理(ms) | CGLIB(ms) |
|---|---|---|
| 创建1000个代理 | 120 | 180 |
| 100万次方法调用 | 45 | 65 |
实战经验:在性能敏感场景,如果类本身有接口,优先使用JDK动态代理;如果需要代理类方法或追求更快的调用速度,可以考虑CGLIB。
4. 大厂面试高频问题与避坑指南
4.1 代理模式的典型面试问题
-
基础概念类:
- 代理模式与装饰器模式的区别?
- 静态代理和动态代理各有什么优缺点?
- JDK动态代理和CGLIB的实现原理是什么?
-
源码分析类:
- Spring AOP是如何选择代理方式的?
- MyBatis的Mapper接口为什么不需要实现类?
- Dubbo的服务引用过程使用了哪种代理技术?
-
场景设计类:
- 如何设计一个支持方法级权限控制的代理?
- 怎样实现一个可插拔的日志代理?
- 代理模式在微服务架构中有哪些应用场景?
4.2 高频踩坑点与应对策略
坑1:代理对象的方法内部调用失效
java复制public class UserService {
public void createUser() {
// 这个updateLog不会走代理逻辑
updateLog();
}
public void updateLog() {
// ...
}
}
解决方案:
- 从ApplicationContext中获取当前代理对象
- 使用AopContext.currentProxy()
坑2:equals/hashCode方法代理问题
java复制Set<SomeInterface> set = new HashSet<>();
SomeInterface proxy1 = createProxy(realObj);
SomeInterface proxy2 = createProxy(realObj);
set.add(proxy1);
set.contains(proxy2); // 可能返回false
解决方案:
- 在InvocationHandler中特殊处理equals/hashCode
- 使用IdentityHashMap代替普通Map/Set
坑3:CGLIB代理导致的类加载问题
code复制Caused by: java.lang.IllegalStateException:
Failed to load class "org.springframework.cglib.proxy.Enhancer"
解决方案:
- 确保spring-core版本与spring-context一致
- 显式引入cglib依赖
4.3 性能优化实战技巧
- 缓存代理对象:代理对象创建成本高,应该缓存复用
- 减少拦截方法:精确配置需要代理的方法,避免无差别拦截
- 选择合适代理:
- 接口代理:JDK动态代理
- 类代理:CGLIB
- 极致性能:Javassist/Byte Buddy
java复制// 使用Byte Buddy创建高性能代理
new ByteBuddy()
.subclass(RealService.class)
.method(ElementMatchers.any())
.intercept(MethodDelegation.to(Interceptor.class))
.make()
.load(getClass().getClassLoader())
.getLoaded();
性能对比数据(百万次调用):
| 代理方式 | 耗时(ms) |
|---|---|
| 直接调用 | 15 |
| JDK代理 | 180 |
| CGLIB | 120 |
| Byte Buddy | 85 |
最后分享一个真实案例:在某电商平台的商品详情页,我们通过CGLIB代理实现了多级缓存策略。代理对象先查本地缓存,未命中查Redis,最后才回源数据库。这个优化将平均响应时间从120ms降低到了45ms。关键点在于合理设置缓存过期时间和优雅降级机制,当Redis不可用时能自动跳过直接查库。
