跳至主要內容

Mr.Hope大约 4 分钟

一、代理模式的"双重人格":隔离与控制

代理模式就像明星的经纪人——控制访问扩展功能。通过代理对象间接操作目标对象,既能实现业务逻辑隔离(如权限校验),又能无缝添加日志、事务等扩展功能[1]open in new window [6]open in new window

静态代理像是定制西装:

// 接口
public interface Payment {
    void pay();
}

// 目标类
class Alipay implements Payment {
    public void pay() { /* 支付逻辑 */ }
}

// 代理类
class PaymentProxy implements Payment {
    private Alipay target;
    
    public void pay() {
        log("支付开始");
        target.pay(); // 核心调用
        log("支付完成");
    }
}

每个接口都需要手动编写代理类,当接口新增方法时,所有代理类必须同步修改,维护成本极高[2]open in new window [12]open in new window

动态代理则是万能裁缝:

  • JDK动态代理:运行时通过反射生成$Proxy0类,代理所有接口方法
  • CGLIB代理:通过ASM生成目标类的子类,连非接口方法也能代理

代理模式内存模型对比静态代理编译期确定类结构,动态代理运行时生成字节码[6]open in new window [40]open in new window


二、动态代理的"武功秘籍"

1. JDK动态代理:接口的艺术

public class LogHandler implements InvocationHandler {
    private Object target; // 目标对象
    
    public Object invoke(Object proxy, Method method, Object[] args) {
        log(method.getName() + "调用开始");
        Object result = method.invoke(target, args);
        log(method.getName() + "调用结束");
        return result;
    }
}

// 生成代理对象
Payment proxy = (Payment) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new LogHandler(target)
);

核心机制

  1. ProxyGenerator生成$Proxy0.class字节码
  2. 代理类继承Proxy并实现目标接口
  3. 通过Method对象反射调用目标方法[40]open in new window [56]open in new window

2. CGLIB代理:继承的魔法

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new MethodInterceptor() {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {
        log("拦截方法:" + method.getName());
        return proxy.invokeSuper(obj, args); // 调用父类方法
    }
});
UserService proxy = (UserService) enhancer.create();

关键技术


三、性能对决:速度与空间的较量

JMH基准测试数据(100万次调用)

方案耗时(ms)内存峰值(MB)适用场景
JDK动态代理12015接口存在、高频更新
CGLIB8522无接口、性能敏感场景
静态代理9512方法少、长期稳定接口[36]open in new window [57]open in new window_

选型建议

  • 有接口:优先JDK动态代理(Java 8+性能优化显著)
  • 无接口/性能敏感:选择CGLIB(注意final方法限制)
  • 高并发场景:考虑静态代理预编译优势[10]open in new window [30]open in new window

四、框架级实战:Spring与Dubbo的智慧

1. Spring AOP的代理策略

<!-- 强制使用CGLIB -->
<aop:config proxy-target-class="true">

Spring根据目标对象是否实现接口自动选择:

2. Dubbo的远程调用

// 服务引用生成代理
ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
reference.setInterface(DemoService.class);
DemoService proxy = reference.get(); // 动态代理对象

通过动态代理封装网络通信细节,客户端像调用本地方法一样使用远程服务[29]open in new window [59]open in new window


五、面试直通车:必知必会

高频对比题

维度JDK动态代理CGLIB代理
实现基础接口继承
性能调用慢(反射)、生成快调用快(FastClass)、生成慢
final限制无影响无法代理final方法
内存消耗高(生成子类)[6]open in new window [25]open in new window_

设计模式辨析


六、陷阱与优化

1. JDK 17+的模块化限制

# 启动参数添加
--add-opens java.base/java.lang=ALL-UNNAMED

Java 9+的模块系统限制了反射访问,需要手动开放权限包[40]open in new window

2. 内存泄漏案例

// 错误示例:未关闭ClassLoader
ClassLoader loader = new URLClassLoader(urls);
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
    loader, interfaces, handler
);
// 长时间运行后PermGen溢出

解决方案:及时回收或使用公共ClassLoader[43]open in new window


延伸阅读

  • 《Effective Java》条目18:接口优于抽象类
  • JEP 416:方法句柄替代反射(JDK 18性能优化)[46]open in new window

掌握代理模式的底层原理与工程实践,是构建高扩展性Java系统的关键。选择代理方案时,需在接口约束、性能需求、维护成本之间寻找平衡点,正如武侠中的"无招胜有招"——理解本质方能游刃有余。