.
Java 反射机制
很多编程语言都有“反射机制”。这项机制能让程序在运行期间 自省(introspect)。
通过这项技术,我们可以在运行期间获取类的属性、方法等元数据,甚至修改类的定义。
Java 的反射机制也一样。
AccessibleObject
AccesibleObject 是使用 Java 反射机制上通常会用到的类。
java.lang.reflect.Constructor、java.lang.reflect.Method、java.lang.reflect.Field 都继承自该类。
Java 9 之后,原则上只有被反射操作的模块和指定的包对反射调用者模块Open,才能使用 setAccessible(Jigsaw)。
所以虽然直接使用 setAccessible 的行为被兼容,但是存在争议。
动态代理
动态代理一种程序运行时动态构建代理、动态处理代理方法调用的机制。
AOP(面向切面编程)、RPC调用包装之类场景就用到了动态代理。
动态代理的实现方式有很多。Java的反射机制就是其中一种。 ASM、cglib、Javassist 都可以实现动态代理。
代理就是对调用目标的一个包装,类似设计模式里的代理模式。对目标代码的调用时通过代理对象完成的,而不是直接调用。
代理机制可以让 调用者 和 实现者 之间解耦;隐藏一些调用者不太关心的操作,提供更友好的访问方式。
JDK Proxy 实现动态代理
JDK Proxy 内部用到了 Java 反射机制 和 ASM。
它基于接口实现代理,即构建一个也实现了目标接口的类。
因为是基于接口的代理,代理对象的类型不是原类型,也不是原类型的子类,所以业务逻辑不能依赖于类继承关系。
这是一个利用 JDK Proxy 实现动态代理的样例。
示例代码中指定代理实例代理所有 HelloImpl 类实现的类:HelloImpl.class.getInterfaces()
如果只想代理 Hello 接口,可以指定为 new Class<?>[]{Hello.class}
MyInvocationHandler.invoke 方法内部调用了实际目标方法 HelloImpl.sayHello。
此处代理方法只是输出了一行日志。可根据需求实现各种逻辑(日志、用户鉴权、全局性异常处理、性能监控、事务处理等)。
这种“全包围”的代理方式类似于 AOP 中的“环绕通知”(如,AspectJ 中的 @Around)。
Java代码
1. public class MyDynamicProxy {
2. public static void main(String[] args) {
3. HelloImpl hello = new HelloImpl();
4. MyInvocationHandler handler = new MyInvocationHandler();
5.
6. // 构造代理类实例
7. Hello proxyHello = (Hello)Proxy.newProxyInstance(
8. HelloImpl.class.getClassLoader(),
9. HelloImpl.class.getInterfaces(),
10. handler);
11.
12. // 调用代理方法
13. proxyHello.sayHello();
14. }
15.
16. interface Hello {
17. void sayHello();
18. }
19.
20. class HelloImpl implements Hello {
21. @Override
22. public void sayHello() {
23. System.out.println("Hello World");
24. }
25. }
26.
27. class MyInvocationHandler implements InvocationHandler {
28. private Object target;
29. public MyInvocationHandler(Object target) {
30. this.target = target;
31. }
32.
33. @Override
34. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
35. System.out.println("Invoking sayHello");
36. Object result = method.invoke(target, args);
37. return result;
38. }
39. }
40. }
cglib 实现动态代理
当被代理类未实现接口,准确地说是被代理方法不属于某个接口时,我们可以用 cglib 来实现代理。
cglib 内部使用了 ASM。
它实现代理的原理是构建一个目标类的子类。与 JDK Proxy 基于接口的方式相比,该方式更近似使用被调用者本身。
Spring AOP 中也可显式指定使用 cglib。
JDK Proxy vs cglib
JDK Proxy 的优势
JDK Proxy 可最小化依赖关系。这可以简化开发与维护:
- 代码实现简单
- 可以随着JDK平滑升级
- JDK通常比 cglib 更可靠
cglib 的优势
- 不受“无接口”的限制
不过,面向接口编程依然是值得推荐的原则。这对解耦及提升可测性方面非常有效。
- 只操作业务所关心的类,不必增加处理其它相关类的工作量
- 高性能
其实 JDK Proxy 的性能也不差,在典型场景中近似 cglib
结论
与通常对待性能的技术选型一样,在没有明确的统计数据与需求前,不必纠结与选何种技术。
可靠性、可维护性、编程工作量都是主要的考虑因素。