一、什么是动态代理

想象一下,你开了一家奶茶店,生意越来越好,但每天要处理的事情也越来越多:记录销售数据、管理库存、处理客户投诉...这时候你可能会想,要是有个"万能助手"能帮你自动处理这些杂事就好了。在Java世界里,动态代理就是这个"万能助手"。

动态代理简单来说,就是在程序运行时动态创建代理对象,代替原始对象处理各种事务。它最大的特点是不需要提前写好代理类,而是在运行时根据需要生成。

举个例子,假设我们有个卖奶茶的接口:

// 技术栈:Java 8+
public interface MilkTeaShop {
    void sellMilkTea(String flavor);  // 卖奶茶
    int checkInventory();            // 检查库存
}

二、JDK动态代理怎么用

JDK动态代理是Java自带的代理技术,使用起来就像请了个专业秘书:

// 技术栈:Java 8+
public class RealMilkTeaShop implements MilkTeaShop {
    @Override
    public void sellMilkTea(String flavor) {
        System.out.println("卖出奶茶:" + flavor);
    }
    
    @Override
    public int checkInventory() {
        System.out.println("当前库存:100杯");
        return 100;
    }
}

// 代理处理类 - 相当于秘书的工作手册
public class ShopHandler implements InvocationHandler {
    private final Object target;  // 被代理的真实对象
    
    public ShopHandler(Object target) {
        this.target = target;
    }
    
    @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;
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        MilkTeaShop realShop = new RealMilkTeaShop();
        
        // 创建代理实例
        MilkTeaShop proxyShop = (MilkTeaShop) Proxy.newProxyInstance(
            realShop.getClass().getClassLoader(),
            realShop.getClass().getInterfaces(),
            new ShopHandler(realShop)
        );
        
        // 通过代理调用方法
        proxyShop.sellMilkTea("珍珠奶茶");
        proxyShop.checkInventory();
    }
}

运行这个程序,你会看到每次方法调用前后都会自动添加日志记录,就像有个秘书在帮你记录工作日志一样。

三、CGLIB动态代理又是怎么回事

有时候我们的"奶茶店"可能没有实现任何接口,这时候JDK动态代理就无能为力了。CGLIB就像是一个更全能的助手,即使没有接口也能工作。

来看个CGLIB的例子:

// 技术栈:Java 8+ (需要引入CGLIB库)
public class SimpleMilkTeaShop {
    public void sellMilkTea(String flavor) {
        System.out.println("卖出奶茶:" + flavor);
    }
    
    public int checkInventory() {
        System.out.println("当前库存:100杯");
        return 100;
    }
}

// CGLIB方法拦截器
public class ShopInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 方法调用前
        System.out.println("【CGLIB记录】开始处理:" + method.getName());
        
        // 调用父类方法
        Object result = proxy.invokeSuper(obj, args);
        
        // 方法调用后
        System.out.println("【CGLIB记录】处理完成:" + method.getName());
        return result;
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SimpleMilkTeaShop.class);
        enhancer.setCallback(new ShopInterceptor());
        
        SimpleMilkTeaShop proxyShop = (SimpleMilkTeaShop) enhancer.create();
        
        proxyShop.sellMilkTea("布丁奶茶");
        proxyShop.checkInventory();
    }
}

CGLIB通过继承目标类来创建代理,所以即使没有接口也能工作。它就像是一个能模仿任何人的超级助手。

四、JDK Proxy和CGLIB的对比

让我们把这两种代理技术放在一起比较一下:

  1. 工作原理

    • JDK Proxy:基于接口,运行时动态生成实现接口的代理类
    • CGLIB:基于继承,运行时动态生成目标类的子类
  2. 性能

    • JDK Proxy:生成速度快,但调用稍慢
    • CGLIB:生成速度慢,但调用快(新版本差距已经很小)
  3. 限制

    • JDK Proxy:只能代理接口
    • CGLIB:不能代理final类和方法
  4. 使用复杂度

    • JDK Proxy:Java自带,无需额外依赖
    • CGLIB:需要引入第三方库

五、实际应用场景

动态代理在实际项目中大有用武之地:

  1. Spring AOP:Spring框架中大量使用动态代理来实现面向切面编程
  2. RPC框架:远程方法调用时生成代理类处理网络通信
  3. 事务管理:在方法调用前后自动处理事务
  4. 日志记录:像我们例子中那样自动记录方法调用
  5. 权限检查:在方法调用前检查用户权限

六、使用注意事项

虽然动态代理很强大,但使用时也要注意:

  1. 性能考虑:大量使用动态代理可能会影响性能
  2. 调试困难:生成的代理类增加了调试难度
  3. 过度使用:不是所有地方都需要代理,避免滥用
  4. final限制:CGLIB无法代理final方法和类
  5. 循环调用:代理类内部调用自身方法不会经过代理

七、如何选择合适的技术

选择JDK Proxy还是CGLIB?可以遵循以下原则:

  1. 如果目标对象实现了接口,优先使用JDK Proxy
  2. 如果没有接口或者需要代理没有接口的第三方类,使用CGLIB
  3. 在Spring中,可以通过配置强制使用CGLIB
  4. 对性能有极高要求的场景,可以测试两种方式选择更优者

八、总结

动态代理就像编程世界里的"万能助手",能够帮我们自动处理各种重复性工作。JDK Proxy是Java自带的"标准助手",而CGLIB则是功能更强大的"高级助手"。理解它们的原理和区别,能够让我们在合适的场景选择合适的技术,写出更优雅、更高效的代码。

记住,动态代理不是银弹,它最适合处理横切关注点(如日志、事务等)。合理使用能让代码更干净,滥用则会让系统变得复杂难懂。希望这篇通俗易懂的讲解能帮助你更好地理解和应用动态代理技术。