动态代理实现的比较
Published:
25 Feb 2014
Category:
Java
动态代理实现的比较
有的时候我们需要拦截方法的调用,来执行我们自己的逻辑。如果你不是Java EE CDI规范的拥趸,也不想使用类似aspectj的AOP框架,你还可以用别的简单有效的方式。
JDK1.5引入了java.lang.reflect.Proxy,你可以通过它给一个指定的接口创建动态代理。程序每次调用动态代理类的时候,都会调用到代理类的InvocationHandler。因此在框架或者库的代码执行之前,你可以动态控制应该执行什么代码。
另外一个JDK代理的实现是字节码框架,比如javassist或者cglib,它们都提供了类似的功能。你可以通过子类来决定你应该调用父类的哪个方法,或者你想要拦截哪个方法。这当然需要引入一个第三方库到你的工程依赖中,并且可能需要时不时的更新下版本,而使用JDK的代理实现,则已经包含在Java的运行环境里面了。
我们来进一步的研究下这三种实现方式。为了比较javassist/cglib和JDK的代理实现,我们需要一个接口以及一个实现,因为JDK的实现机制只支持接口方式,不能通过子类来完成:
为了能把代理对象上的方法调用委托给实际的对象,我们先创建了一个Example对象的实例,放到一个final变量里,然后通过InvocationHandler来调用它。
从这个代码示例里面可以看到,创建一个代理非常简单:调用newProxyInstance静态方法,并且提供一个类加载器,一个代理可能实现的接口的数组,还有一个InvocationHandler的实现。为了方便演示,我们这个实现只是把调用转给了前面刚创建的一个Example实例。在实际过程中,你可以根据调用 方法名和参数来执行一些更复杂的操作。
现在我们来看下javassist的实现:
这里我们创建了一个ProxyFactory,它需要知道哪个类需要生成子类。然后我们用这个代理工厂生成了一个Class对象,这个对象是可以无限使用的。这里的MethodHandler的作用和InvocationHandler差不多,当实例的方法被调用的时候,它们也会对应的被调起。这里我们还是只调用了一下Example的实例。
最后一个但同样也很重要的,我们来看下cglib版的实现:
在cglib里面,我们用Enhancer来实现一个指定的接口,它需要传入一个MethodInterceptor的实例。这个回调方法的实现跟javassist的例子差不多,也是通过反射的API调用了Example的一个实现。
现在我们看到了三个版本的不同实现,我们来分析下它们运行时的表现。这里写了一个简单的单元测试,用来衡量这三种实现的执行时间:
我们对这个操作重复执行多次,来对JVM进行压测,以便Hotspot编译器对热点路径生成本地代码。下面的图表是这三种实现的平均运行时间:
为了更好的评估各个代理实现的效率,我们也列出了Example对象的方法正常调用的执行时间(“No Proxy”)。首先我们可以确定的是,代理的实现和原生实现相比,效率至少慢了十倍。同时我们还注意到几种代理实现的差别。令人意外的是JDK的代理类和cglib的实现一样快。只有javassist的版本,和cglib想比,差了大约一倍。
结论:运行时代理非常简单易用,你可以使用不同的实现方式。JDK的代理只支持接口而javassist和cglib允许你通过子类来实现。它们在运行时的性能表现比标准实现要慢十倍,同时这三种实现的表现也各不相同。
原文链接