.

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的反射机制就是其中一种。 ASMcglibJavassist 都可以实现动态代理。

 

代理就是对调用目标的一个包装,类似设计模式里的代理模式。对目标代码的调用时通过代理对象完成的,而不是直接调用。

代理机制可以让 调用者 和 实现者 之间解耦;隐藏一些调用者不太关心的操作,提供更友好的访问方式。

 

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

结论

与通常对待性能的技术选型一样,在没有明确的统计数据与需求前,不必纠结与选何种技术。

可靠性、可维护性、编程工作量都是主要的考虑因素。