Java 動態代理詳解:定義、實現與應用場景
Java 动态代理详解:定义、实现与应用场景
一场"偷梁换柱"的技术探险
上个月接到一个需求:给公司所有的数据库操作加上日志记录,但不能改动现有的 DAO 层代码。我当时就懵了,这不是要我"空手套白狼"吗?
直到老同事神秘一笑:“试试动态代理吧,保证让你体验一把’狸猫换太子’的快感。”
什么是动态代理?一个"替身演员"的故事
想象一下,你是个大明星,但又不想亲自出席每个商业活动。这时候你需要一个替身,这个替身:
- 长得像你(实现相同接口)
- 会模仿你的行为(调用真实方法)
- 还能做一些你不想做的事(添加额外功能)
Java 动态代理就是这样的"替身演员",它能在运行时动态创建一个代理对象,替你干活的同时还能偷偷加点料。
初次尝试:JDK 动态代理登场
我决定先用 JDK 自带的动态代理试试水。核心思路很简单:创建一个 InvocationHandler,在里面做手脚。
public class LoggingHandler implements InvocationHandler {
private Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行方法: " + method.getName());
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args); // 调用真实方法
System.out.println("耗时: " + (System.currentTimeMillis() - startTime) + "ms");
return result;
}
}
使用起来也很简单,就是把原来的对象"包装"一下:
// 创建代理对象
UserService userService = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new LoggingHandler(userService)
);
// 使用代理对象,自动带日志功能
proxy.getUserById(123); // 这里会自动记录日志
踩坑瞬间:接口限制让我措手不及
正当我沾沾自喜时,产品经理过来说:“还有个工具类也要加日志,但它没实现接口啊!”
我一查,发现 JDK 动态代理有个硬性要求:被代理的类必须实现接口。这就像替身演员只能模仿有剧本的角色,遇到即兴表演就抓瞎了。
怎么办?这时候 CGLIB 闪亮登场!
转折点:CGLIB 拯救无接口类
CGLIB 是个更厉害的"变身术",它通过字节码技术直接继承目标类,创建子类代理。即使没有接口,也能完美替身。
public class CGLibProxy 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代理 - 方法执行完毕");
return result;
}
// 创建代理对象的工厂方法
public static Object createProxy(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new CGLibProxy());
return enhancer.create();
}
}
这下好了,不管有没有接口,都能愉快地加日志了。
经验启示:两种代理的选择策略
经过这次折腾,我总结了一套"代理选择法则":
场景 | JDK 动态代理 | CGLIB 代理 |
---|---|---|
有接口 | ✅ 首选,性能更好 | ✅ 可用,但略重 |
无接口 | ❌ 无法使用 | ✅ 唯一选择 |
final 类 | ❌ 无法使用 | ❌ 无法继承 |
性能要求 | 🚀 更快 | 🐌 稍慢 |
实际项目中,Spring AOP 就是这么干的:有接口用 JDK 代理,没接口用 CGLIB 代理,自动帮你选择最合适的方案。
应用场景:代理的"十八般武艺"
动态代理在实际开发中简直是"万金油":
日志记录:我们刚才演示的场景,无侵入式添加日志
权限控制:在方法执行前检查用户权限
事务管理:Spring 的 @Transactional 注解就是这么实现的
缓存处理:自动缓存方法返回值,提升性能
性能监控:统计方法执行时间,找出性能瓶颈
重试机制:失败时自动重试,提高系统稳定性
收获与感悟
这次动态代理的探险让我深刻体会到:好的技术就像魔法,能让不可能变成可能。
原本需要修改几十个类的需求,用动态代理几行代码就搞定了。这种"四两拨千斤"的感觉,就是编程的魅力所在。
更重要的是,我明白了设计模式的精髓:不是为了炫技,而是为了解决实际问题。代理模式让我们能够在不修改原有代码的前提下,灵活地扩展功能,这在企业级开发中简直就是救命稻草。
下次再遇到"不能改代码但要加功能"的需求,我会淡定地说:“小case,动态代理了解一下?”
共同學習,寫下你的評論
評論加載中...
作者其他優質文章