有时需要拦截某些方法调用,以便每次调用被拦截方法时都执行自己的逻辑。 如果您不属于Java EE的CDI领域,并且不想使用诸如Aspectj之类的AOP框架,那么您将有一个简单而有效的替代方法。
从1.5版开始,JDK附带了类java.lang.reflect.Proxy,该类使您可以为给定的接口创建动态代理。 每次应用程序调用代理上的方法时,都会调用位于动态创建的类后面的InvocationHandler。 因此,您可以在调用某个框架或库的代码之前动态控制执行什么代码。
在JDK的代理实现旁边,像javassist或cglib这样的字节码框架提供了类似的功能。 在这里,您甚至可以对现有的类进行子类化,并确定要转发给超类的实现的方法以及要拦截的方法。 当然,这会带来项目依赖的另一个库的负担,并且可能需要不时更新,而运行时环境中已经包含了JDK的Proxy实现。
因此,让我们仔细看看并尝试这三种选择。 为了将javassist和cglib的代理与JDK实现进行比较,我们需要一个由简单类实现的接口,因为JDK机制仅支持接口,而没有子类:
public interface IExample {void setName(String name);
}public class Example implements IExample {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
为了将代理上的方法调用委托给某个实际对象,我们创建了上面的Example类的实例,并通过最终声明的变量在InvocationHandler中对其进行了调用:
final Example example = new Example();
InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(example, args);}
};
return (IExample) Proxy.newProxyInstance(JavaProxy.class.getClassLoader(), new Class[]{IExample.class}, invocationHandler);
从代码示例中您可以看到,代理的创建非常简单:调用静态方法newProxyInstance()并提供ClassLoader,应由代理实现的接口数组以及InvocationHandler接口的实例。 为了演示起见,我们的实现仅转发我们之前创建的Example实例。 但是,在现实生活中,您当然可以执行更高级的操作,以评估例如方法名称或其参数。
现在我们来看一下使用javassist完成相同操作的方式:
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(Example.class);
Class aClass = factory.createClass();
final IExample newInstance = (IExample) aClass.newInstance();
MethodHandler methodHandler = new MethodHandler() {@Overridepublic Object invoke(Object self, Method overridden, Method proceed, Object[] args) throws Throwable {return proceed.invoke(newInstance, args);}
};
((ProxyObject)newInstance).setHandler(methodHandler);
return newInstance;
在这里,我们有一个ProxyFactory,它想知道应该为哪个类创建子类。 然后,我们让ProxyFactory创建一个整个类,该类可以根据需要多次重用。 这里的MethodHandler与InvocationHandler相似,后者是为实例的每次方法调用而调用的。 在这里,我们再次将调用转发到之前创建的Example实例。
最后但并非最不重要的一点,让我们看一下cglib的代理:
final Example example = new Example();
IExample exampleProxy = (IExample) Enhancer.create(IExample.class, new MethodInterceptor() {@Overridepublic Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {return method.invoke(example, args);}
});
return exampleProxy;
在cglib世界中,我们有一个Enhancer类,可用于通过MethodInterceptor实例实现给定的接口。 回调方法的实现看起来与javassist示例中的实现非常相似。 我们只是通过反射API将方法调用转发到Example的现有实例。
既然我们已经看到了三种不同的实现,我们还希望评估它们的运行时行为。 因此,我们编写了一个简单的单元测试,它测量了每个实现的执行时间:
@Test
public void testPerformance() {final IExample example = JavaProxy.createExample();long measure = TimeMeasurement.measure(new TimeMeasurement.Execution() {@Overridepublic void execute() {for (long i = 0; i < JavassistProxyTest.NUMBER_OF_ITERATIONS; i++) {example.setName("name");}}});System.out.println("Proxy: "+measure+" ms");
}
我们选择大量的迭代,以便强调JVM并让HotSpot编译器为经常执行的段落创建本机代码。 下表显示了三种实现的平均运行时间:
为了完全显示代理实现的影响,该图表还显示了对Example对象(“无代理”)进行标准方法调用的执行时间。 首先,我们可以记录下来,代理实现比方法本身的普通调用慢大约10倍。 但是我们也注意到三种代理解决方案之间的差异。 令人惊讶的是,JDK的Proxy类几乎与cglib实现一样快。 只有javassist的退出时间是cglib的两倍左右。
结论:运行时代理易于使用,您有不同的处理方式。 JDK的代理仅支持接口代理,而javassist和cglib允许您对现有类进行子类化。 代理的运行时行为比标准方法调用慢大约10倍。 三种解决方案在运行时间方面也有所不同。
翻译自: https://www.javacodegeeks.com/2014/01/implementing-dynamic-proxies-a-comparison.html