一、什么是动态代理
想象一下,你开了一家奶茶店,生意越来越好,但每天要处理的事情也越来越多:记录销售数据、管理库存、处理客户投诉...这时候你可能会想,要是有个"万能助手"能帮你自动处理这些杂事就好了。在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的对比
让我们把这两种代理技术放在一起比较一下:
工作原理:
- JDK Proxy:基于接口,运行时动态生成实现接口的代理类
- CGLIB:基于继承,运行时动态生成目标类的子类
性能:
- JDK Proxy:生成速度快,但调用稍慢
- CGLIB:生成速度慢,但调用快(新版本差距已经很小)
限制:
- JDK Proxy:只能代理接口
- CGLIB:不能代理final类和方法
使用复杂度:
- JDK Proxy:Java自带,无需额外依赖
- CGLIB:需要引入第三方库
五、实际应用场景
动态代理在实际项目中大有用武之地:
- Spring AOP:Spring框架中大量使用动态代理来实现面向切面编程
- RPC框架:远程方法调用时生成代理类处理网络通信
- 事务管理:在方法调用前后自动处理事务
- 日志记录:像我们例子中那样自动记录方法调用
- 权限检查:在方法调用前检查用户权限
六、使用注意事项
虽然动态代理很强大,但使用时也要注意:
- 性能考虑:大量使用动态代理可能会影响性能
- 调试困难:生成的代理类增加了调试难度
- 过度使用:不是所有地方都需要代理,避免滥用
- final限制:CGLIB无法代理final方法和类
- 循环调用:代理类内部调用自身方法不会经过代理
七、如何选择合适的技术
选择JDK Proxy还是CGLIB?可以遵循以下原则:
- 如果目标对象实现了接口,优先使用JDK Proxy
- 如果没有接口或者需要代理没有接口的第三方类,使用CGLIB
- 在Spring中,可以通过配置强制使用CGLIB
- 对性能有极高要求的场景,可以测试两种方式选择更优者
八、总结
动态代理就像编程世界里的"万能助手",能够帮我们自动处理各种重复性工作。JDK Proxy是Java自带的"标准助手",而CGLIB则是功能更强大的"高级助手"。理解它们的原理和区别,能够让我们在合适的场景选择合适的技术,写出更优雅、更高效的代码。
记住,动态代理不是银弹,它最适合处理横切关注点(如日志、事务等)。合理使用能让代码更干净,滥用则会让系统变得复杂难懂。希望这篇通俗易懂的讲解能帮助你更好地理解和应用动态代理技术。
评论