在Java开发中,代理模式就像明星和经纪人的关系。想象一下,当你想邀请周杰伦来开演唱会时,你不会直接联系他本人,而是通过他的经纪人进行沟通。这个经纪人就是代理,他不仅负责安排演出,还会处理合同签订、场地预订等周边事务,让周杰伦可以专注于唱歌这个核心业务。
代理模式的核心价值在于:
提示:代理模式属于结构型设计模式,与装饰器模式有相似之处,但代理模式更强调访问控制,而装饰器模式侧重功能叠加。
静态代理是最直观的代理实现方式,我们需要手动创建代理类。以演唱会场景为例:
java复制// 1. 定义公共接口
public interface Star {
void sing();
void discussContract();
}
// 2. 实现真实角色
public class JayChou implements Star {
@Override
public void sing() {
System.out.println("周杰伦演唱《青花瓷》");
}
@Override
public void discussContract() {
// 真实明星可能不擅长商务谈判
}
}
// 3. 创建代理类
public class StarAgent implements Star {
private Star star;
public StarAgent(Star star) {
this.star = star;
}
@Override
public void sing() {
System.out.println("代理:确认档期和场地");
star.sing();
System.out.println("代理:结算演出费用");
}
@Override
public void discussContract() {
System.out.println("代理:专业谈判合同条款");
// 可能不需要调用明星本人的方法
}
}
在实际项目中,静态代理会面临几个明显痛点:
我曾经在一个电商项目中看到过这样的代码:为UserService、ProductService等十几个服务类都创建了静态代理类,后来接口新增方法时,开发人员不得不修改二十多个文件,这种维护成本是难以接受的。
JDK动态代理利用了Java的反射机制,在运行时动态创建代理类。其核心在于java.lang.reflect.Proxy类和InvocationHandler接口:
java复制public class DynamicAgent implements InvocationHandler {
private Object target;
public DynamicAgent(Object target) {
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@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;
}
}
JDK动态代理在运行时生成的代理类大致相当于以下代码:
java复制public final class $Proxy0 extends Proxy implements Star {
private static Method m1;
private static Method m2;
private static Method m3;
static {
try {
m1 = Class.forName("Star").getMethod("sing");
// 其他方法初始化...
} catch (Exception e) {
// 异常处理
}
}
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public void sing() {
try {
h.invoke(this, m1, null);
} catch (Throwable e) {
// 异常处理
}
}
}
关键点说明:
Proxy并实现目标接口InvocationHandler虽然JDK动态代理解决了静态代理的扩展性问题,但它有两个硬性限制:
在Spring框架中,当目标类实现了接口时,默认就会使用JDK动态代理。这也是为什么Spring官方推荐面向接口编程的原因之一。
CGLib(Code Generation Library)采用了不同的实现策略 - 它通过继承目标类并重写方法的方式创建代理:
java复制// 真实类(无需实现接口)
public class JayChou {
public void sing() {
System.out.println("周杰伦演唱《七里香》");
}
}
// CGLib代理工厂
public class CglibProxy implements MethodInterceptor {
public Object getProxy(Class clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@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;
}
}
在Spring中,当目标类没有实现接口时,就会自动使用CGLib来创建代理。这也是为什么即使没有接口,Spring的@Transactional等注解也能正常工作。
我曾经遇到一个坑:尝试代理一个只有带参构造器的类时,CGLib抛出了异常。解决方法要么添加无参构造器,要么使用Objenesis库来绕过构造器限制。
| 特性 | 静态代理 | JDK动态代理 | CGLib动态代理 |
|---|---|---|---|
| 实现方式 | 手动编写代理类 | 接口+反射 | 继承+方法重写 |
| 编译期依赖 | 需要 | 不需要 | 不需要 |
| 目标类要求 | 实现接口 | 实现接口 | 无要求 |
| 性能 | 最好 | 中等 | 较高 |
| 方法过滤 | 困难 | 相对容易 | 灵活 |
| 代理final类/方法 | 不可行 | 不可行 | 不可行 |
静态代理适用场景:
JDK动态代理首选时机:
CGLib动态代理最佳选择:
在Spring AOP中,框架会根据目标类是否实现接口自动选择代理方式。这也是为什么Spring Boot 2.x开始默认使用CGLib - 因为现代应用中使用接口的情况减少了。
在实际开发中,我们经常需要对同一个目标对象应用多个代理:
java复制// 创建目标对象
Star jay = new JayChou();
// 第一个代理:日志记录
Star logProxy = (Star) new LogHandler(jay).getProxy();
// 第二个代理:事务管理
Star txProxy = (Star) new TxHandler(logProxy).getProxy();
// 使用最终代理
txProxy.sing();
这种嵌套代理模式在AOP框架中很常见,但要注意:
问题1:代理对象方法没有被增强
可能原因:
问题2:Spring AOP失效
常见场景:
解决方案:
在我的性能调优经验中,曾经通过将动态代理改为静态代理,使某个核心服务的吞吐量提升了约15%。当然,这种优化需要在可维护性和性能之间做好权衡。
Spring AOP的代理创建流程:
关键代码片段:
java复制// DefaultAopProxyFactory
public AopProxy createAopProxy(AdvisedSupport config) {
if (config.isOptimize() || config.isProxyTargetClass() ||
hasNoUserSuppliedProxyInterfaces(config)) {
return new ObjenesisCglibAopProxy(config);
}
return new JdkDynamicAopProxy(config);
}
MyBatis通过JDK动态代理将接口调用转换为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) {
// 将方法调用转换为SQL执行
return execute(method, args);
}
}
RPC框架通常使用动态代理将本地接口调用转换为远程通信:
java复制public class RpcProxy implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) {
// 1. 序列化参数
byte[] data = serialize(method, args);
// 2. 网络传输
byte[] result = transport.send(data);
// 3. 反序列化结果
return deserialize(result);
}
}
这种设计使得远程调用对开发者透明,是代理模式的经典应用场景。
虽然两者都通过包装对象来增强功能,但它们的关注点不同:
例如,权限校验代理可能拒绝某些请求,而日志装饰器总是会记录日志并调用原方法。
适配器模式关注接口转换,而代理模式保持接口一致:
java复制// 适配器:转换接口
class Adapter implements Target {
private Adaptee adaptee;
void request() {
adaptee.specificRequest();
}
}
// 代理:增强功能
class Proxy implements Subject {
private RealSubject realSubject;
void request() {
// 前置处理
realSubject.request();
// 后置处理
}
}
可以结合使用这两种模式,让代理的增强逻辑可配置:
java复制public class DynamicProxy implements InvocationHandler {
private Object target;
private Interceptor interceptor; // 策略接口
public Object invoke(Object proxy, Method method, Object[] args) {
return interceptor.intercept(new Invocation(target, method, args));
}
}
这种组合在拦截器链设计中非常有用。
在电商系统中,我们实现了一个方法级性能监控代理:
java复制public class MonitorProxy implements InvocationHandler {
private static final Logger logger = LoggerFactory.getLogger(MonitorProxy.class);
private Object target;
public Object invoke(Object proxy, Method method, Object[] args) {
long start = System.currentTimeMillis();
try {
return method.invoke(target, args);
} finally {
long cost = System.currentTimeMillis() - start;
if (cost > 100) { // 慢方法警告
logger.warn("Slow method: {} cost {}ms", method.getName(), cost);
}
}
}
}
这个代理帮助我们发现了多个性能瓶颈,特别是商品搜索接口中的慢查询问题。
在读写分离场景中,我们使用代理自动路由数据源:
java复制public class DataSourceRouterProxy implements InvocationHandler {
private Object target;
private String readDataSource;
private String writeDataSource;
public Object invoke(Object proxy, Method method, Object[] args) {
String methodName = method.getName();
boolean isRead = methodName.startsWith("get") || methodName.startsWith("query");
try {
DataSourceContext.set(isRead ? readDataSource : writeDataSource);
return method.invoke(target, args);
} finally {
DataSourceContext.clear();
}
}
}
这种设计使得业务代码无需关心数据源切换,保持了代码的简洁性。
为减轻数据库压力,我们实现了方法级缓存代理:
java复制public class CacheProxy implements InvocationHandler {
private Object target;
private Cache cache;
public Object invoke(Object proxy, Method method, Object[] args) {
String key = generateCacheKey(method, args);
Object value = cache.get(key);
if (value == null) {
value = method.invoke(target, args);
cache.put(key, value);
}
return value;
}
}
这个代理将商品信息的查询性能提升了10倍以上,特别是在促销活动期间效果显著。
当需要序列化代理对象时,可能会遇到NotSerializableException。解决方案:
InvocationHandler实现SerializableSerializable接口:java复制Enhancer enhancer = new Enhancer();
enhancer.setInterfaces(new Class[]{Serializable.class});
代理对象可能会破坏equals/hashCode契约,解决方法:
java复制public Object invoke(Object proxy, Method method, Object[] args) {
if ("equals".equals(method.getName())) {
// 特殊处理equals方法
return args[0] == proxy;
}
if ("hashCode".equals(method.getName())) {
// 返回固定hashCode
return System.identityHashCode(proxy);
}
// 其他方法处理...
}
在Spring中,代理可能导致循环依赖问题。典型场景:
java复制@Service
class ServiceA {
@Autowired ServiceB b;
@Transactional
void methodA() {}
}
@Service
class ServiceB {
@Autowired ServiceA a; // 循环依赖
}
解决方案:
@Lazy延迟加载Java 16引入了InvocationHandler的替代方案 - InvocationHandler的替代方案 - MethodHandle,性能更高:
java复制public class MethodHandleProxy {
private static final Lookup lookup = MethodHandles.lookup();
public static Object createProxy(Object target) throws Exception {
MethodType type = MethodType.methodType(Object.class, Object.class, Method.class, Object[].class);
MethodHandle mh = lookup.findVirtual(target.getClass(), "invoke", type);
return Proxy.newProxyInstance(..., (proxy, method, args) -> mh.invoke(target, method, args));
}
}
除了CGLib,还有其他字节码操作库:
| 库名称 | 特点 | 适用场景 |
|---|---|---|
| Byte Buddy | API友好,性能优异 | 现代Java应用 |
| ASM | 底层控制,性能最好 | 框架开发,性能敏感场景 |
| Javassist | 简单易用,支持源码级操作 | 快速原型开发,教育场景 |
在响应式编程中,代理模式有新的应用形式。例如Project Reactor的Mono/Flux可以看作是一种代理:
java复制Mono<User> userMono = userService.getUser(id)
.doOnSubscribe(s -> log.info("Start request"))
.doOnNext(u -> log.info("User found: {}", u))
.doOnError(e -> log.error("Error occurred", e));
这种"代理"通过操作符链式调用,实现了对原始数据流的增强和控制。