文章目录
一、被忽视的日常魔法
(敲黑板!)各位Javaer每天都在用Spring框架,但你们知道@Transactional注解背后隐藏着怎样的魔法吗?当我们调用加了事务注解的方法时,那个能自动开启/提交事务的神秘力量,正是动态代理在发挥作用!!!
二、代理模式的前世今生
2.1 静态代理:直男的笨拙爱
先看这段经典代码:
interface Subject {
void request();
}
class RealSubject implements Subject {
public void request() {
System.out.println("处理真实请求");
}
}
class ProxySubject implements Subject {
private RealSubject realSubject;
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
System.out.println("前置处理");
realSubject.request(); // 重点在这!!!
System.out.println("后置处理");
}
}
(灵魂拷问)这种写法有什么致命缺陷?每增加一个方法就要重写代理类!这在大型项目中简直是噩梦(别问我是怎么知道的…)
2.2 动态代理的华丽登场
JDK动态代理的典型用法:
public class DynamicProxyHandler implements InvocationHandler {
private Object target;
public DynamicProxyHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前");
Object result = method.invoke(target, args); // 核心调用
System.out.println("方法执行后");
return result;
}
}
(关键点预警)这里用到了反射机制,但性能问题怎么破?后面会讲到优化方案!
三、底层原理大起底
3.1 字节码生成的魔法时刻
当调用Proxy.newProxyInstance()时:
- 通过ProxyGenerator生成代理类的字节码
- 使用Unsafe类直接加载到内存
- 生成的新类继承Proxy并实现目标接口
(黑科技预警)可以用这个代码查看生成的类:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
3.2 方法调用的完整链路
调用流程示意图:
客户端调用 -> 代理对象 -> InvocationHandler -> 反射调用真实对象
(性能陷阱)这里比直接调用多经过3层调用栈,高频场景要注意!
四、CGLIB的另类实现
4.1 继承大法好
CGLIB的典型用法:
public class MyMethodInterceptor implements MethodInterceptor {
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;
}
}
(重要区别)这里用的是方法代理而不是反射,性能比JDK动态代理高约20%!
4.2 性能对比实测
测试环境:i7-11800H,JDK17,循环调用100万次
代理类型 | 耗时(ms) | 内存占用 |
---|---|---|
JDK代理 | 158 | 35MB |
CGLIB | 126 | 48MB |
直接调用 | 58 | 12MB |
(血泪教训)选择代理类型时要根据具体场景权衡!
五、Spring的选择策略
Spring框架的代理选择逻辑:
// 来自AbstractAutoProxyCreator
protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors) {
if (!this.proxyTargetClass) {
if (isInterfaceProxied(beanClass)) {
return new JdkDynamicAopProxy(...); // JDK代理
}
}
return new ObjenesisCglibAopProxy(...); // CGLIB代理
}
(最佳实践)强制使用CGLIB的方法:在配置类加@EnableAspectJAutoProxy(proxyTargetClass = true)
六、常见问题排雷指南
6.1 代理失效的N种姿势
- 类内部方法调用(this.xxx())
- final方法无法代理
- static方法不会被拦截
- 私有方法不被代理
6.2 性能优化三板斧
- 缓存Method对象
- 减少拦截器链长度
- 对高频方法做白名单
七、新型代理技术展望
Java 17引入的动态代理新特性:
- 支持接口的私有方法
- 更好的模块化支持
- 与Project Loom的虚拟线程兼容性优化
(未来已来)GraalVM的提前编译技术可以预生成代理类,彻底消除运行时生成的开销!
八、总结与选择建议
最后来个决策树:
是否需要代理类? → 是 → 是否有接口?
↓ ↓
否 是 → 用JDK动态代理
↓ ↓
用CGLIB 需要高性能? → 是 → 考虑字节码增强框架
(终极忠告)不要为了炫技而滥用动态代理!就像金箍棒虽好,日常开发还是多用设计模式更稳妥~