关于java动态代理和反射学习总结
JDK动态代理-CGLIB动态代理-java反射机制
JDK 动态代理和 CGLIB 动态代理有什么区别?
JDK 动态代理
基于接口的,所以要求代理类一定是有定义接口的
CGLIB
基于 ASM 字节码生成工具,通过继承的方式生成目标类的子类来实现代理类,所以要注意 final 方法
二者之间的性能会随着 JDK 版本的不同而不同
jdk6:运行次数较少时,jdk 动态代理和 cglib 差距不明显,甚至更快一些;而当调用次数增加之后,cglib 表现稍微更快一些
jdk7:运行次数较少(1,000,000)的情况下,jdk 动态代理比 cglib 快了差不多 30%;而当调用次数增加之后(50,000,000),动态代理比 cglib 快了接近 1 倍
jdk8 表现和 jdk7 基本一致
表格一览:
| 对比项 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 代理方式 | 基于 接口 | 基于 继承类(子类) |
| 要求 | 必须有接口 | 不需要接口,但类不能是 final |
| 底层技术 | 反射 + Proxy | ASM 字节码增强 |
| 性能 | JDK8 以前慢;JDK9+ 优化后差距小 | 快(因为直接生成子类) |
| 常用场景 | Spring AOP 中代理接口 | Spring AOP 中代理没有接口的类 |
| 限制 | 只能代理接口方法 | 无法代理 final 类、final 方法 |
JDK动态代理工作原理
- 基于接口的代理,得有一个接口才可以用
- 生成一个
Proxy.newProxyInstance() - 调用方法时,会走到
InvocationHandler.invoke() - 在
invoke()中增强逻辑(日志、事务、权限) - 再调用目标方法
JDK 动态代理在运行时会利用Proxy.newProxyInstance()方法,动态创建一个实现了该接口的代理类的实例,这个代理类是在内存中生成,是看不到它的.java或者.class文件的。
当我们调用代理对象任何一个方法的时候,这个调用都会转发到InvocationHandler.invoke()方法中,在invoke()方法中,我们可以在调用原始方法的前后,添加我们自己的增强逻辑。
JDK 动态代理的核心是:
- Proxy.newProxyInstance(...) —— 通过反射创建代理对象
- InvocationHandler.invoke(...) —— 通过反射执行目标方法
JDK动态代理会在运行的时候为接口生成一个代理类,当调用代理对象的时候,本质是反射调用目标方法
代码示例
定义一个接口:
▼java复制代码public interface Service { void perform(); }
接口实现:
▼java复制代码public class ServiceImpl implements Service{ @Override public void perform() { System.out.println("接口实现1"); } }
基于InvocationHandler实现
▼java复制代码public class ServiceInvocationHandler implements InvocationHandler { private final Object target; public ServiceInvocationHandler(Object target) { this.target = target; } // 可以添加增强逻辑,在本invoke方法中添加 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before method invoke"); Object result = method.invoke(target, args); // 反射调用 System.out.println("After method invoke"); return result; } }
动态代理测试:
▼java复制代码public class DynamicProxyDemo { public static void main(String[] args) { Service target = new ServiceImpl(); Service proxy = (Service) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new ServiceInvocationHandler(target) ); proxy.perform(); } }
使用JDK proxy代理对象,必须基于接口
CGLIB动态代理工作原理
- 使用
ASM生成目标的子类 - 方法调用被
MethodInterceptor.intercept()拦截 - 在
intercept中增强逻辑 - 再反射调用原方法
CGLIB 是生成子类 → 不能代理 final 类和 final 方法。
CGLIB 动态代理在运行的时候,基于ASM动态生成目标类的子类作为代理类,然后,它会重写所有父类(也就是被代理类)的非final、private和static方法。
当我们调用代理对象任何一个方法的时候,这个调用都会被CGLIB的MethodInterceptor.intercept()方法所拦截,在intercept()方法中,我们可以在调用原始的父类方法的前后,添加我们自己的增强逻辑。
代码演示
Service类
▼java复制代码public class Service { public void perform() { System.out.println("lantz.com"); } }
ServiceMethodInterceptor类基于MethodInterceptor接口实现
▼java复制代码public class ServiceMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before method invoke"); Object result = proxy.invokeSuper(obj, args); System.out.println("After method invoke"); return result; } }
通过proxy.invokeSuper() 调用父类(即真实对象)逻辑,体现了 CGLIB 是基于“继承”
Cglib动态代理测试:
▼java复制代码public class CglibDynamicProxyDemo { public static void main(String[] args) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Service.class); enhancer.setCallback(new ServiceMethodInterceptor()); Service proxy = (Service) enhancer.create(); proxy.perform(); } }
结果:
▼tex复制代码Before method invoke lantz.com After method invoke
Spring AOP 选择原则
| 条件 | 使用代理 |
|---|---|
| bean 有接口 | JDK 动态代理 |
| bean 没有接口 | CGLIB 动态代理 |
开启 proxy-target-class=true | 强制使用 CGLIB |
什么是Java反射机制?
Java 的反射机制是指在运行时获取类(Class)的结构信息(如方法、字段、构造函数)并操作对象的一种机制。
反射 = 运行时查看 + 运行时修改 + 运行时调用
如何应用反射?
反射机制提供了在运行时动态创建对象、调用方法、访问字段等功能,而无需在编译时知道这些类的具体信息
基本概念
Class 类:反射机制的核心,通过 Class 类的实例可以获取类的各种信息
反射机制的优点
- 可以动态获取类的信息,不需要在编译时就知道类的信息
- 可以动态创建信息,不需要在编译时就知道对象的类型
- 可以动态调用对象的属性和方法,在运行时动态地改变对象的行为
反射的最佳实践
限制访问
尽量避免过度依赖反射,尤其是在性能关键的代码中,因为它设计动态代理和方法调用
使用缓存
缓存反射获取的类、方法、字段等信息,减少反射操作的频率
遵循设计原则
在设计系统时,尽量使用更稳定和易于维护的设计方案,只有在确实需要时才使用反射
反射的使用
1)获取Class对象
获取类的名称、父类、接口等信息
▼java复制代码Class<?> clazz = Class.forName("com.mianshiya.MyClass"); // 或者 Class<?> clazz = MyClass.class; // 或者 Class<?> clazz = obj.getClass();
2)创建对象
通过clazz.newInstance()或者constructor.newInstance()创建对象
▼java复制代码Object obj = clazz.newInstance(); // 已过时 Constructor<?> constructor = clazz.getConstructor(); Object obj = constructor.newInstance();
3)访问字段
通过Field获取类的字段和设置字段信息
▼java复制代码Field field = clazz.getField("myField"); field.setAccessible(true); // 允许访问 private 字段 Object value = field.get(obj); field.set(obj, newValue);
4)调用方法
通过Method类调用对象的方法
▼java复制代码Method method = clazz.getMethod("myMethod", String.class); Object result = method.invoke(obj, "param");
The end……
