在本文中,我将向您展示导致真正的Java功能(代理的使用)的途径。
它们无处不在,但只有少数人知道它们。 Hibernate用于延迟加载实体, Spring用于AOP , LambdaJ用于DSL ,仅举几例:它们都使用了隐藏的魔术。 这些是什么? 它们是Java的动态代理。
每个人都知道GOF 代理设计模式:
通过充当传递实体或占位符对象,可以进行对象级访问控制。
同样,在Java中,动态代理是一个实例,它充当对真实对象的传递。 这种强大的模式使您可以从调用者的角度更改实际行为,因为方法调用可以被代理拦截。
纯Java代理
纯Java代理具有一些有趣的属性:
- 它们基于接口的运行时实现
- 它们是公开的,最终的而不是抽象的
- 它们扩展了
java.lang.reflect.Proxy
在Java中,代理本身并不像代理的行为那么重要。 后者是在java.lang.reflect.InvocationHandler
的实现中完成的。 它只有一个方法可以实现:
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)
-
proxy
:在其上调用方法的代理实例 -
method
:与在代理实例上调用的接口方法相对应的Method
实例。Method
对象的声明类将是在其中声明该Method
的接口,它可以是代理类通过其继承该方法的代理接口的超接口。 -
args
:对象数组,其中包含在代理实例的方法调用中传递的参数的值;如果接口方法不接受任何参数,则为null
。 基本类型的参数包装在适当的基本包装器类的实例中,例如java.lang.Integer
或java.lang.Boolean
让我们举一个简单的例子:假设我们想要一个不能添加元素的List
。 第一步是创建调用处理程序:
publicclassNoOpAddInvocationHandlerimplementsInvocationHandler{
privatefinalListproxied;
publicNoOpAddInvocationHandler(Listproxied){
this.proxied=proxied;
}
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
if(method.getName().startsWith("add")){
returnfalse;
}
returnmethod.invoke(proxied,args);
}
}
invoke
方法将拦截方法调用,并且如果该方法以“ add”开头,则不执行任何操作。 否则,它将把调用传递给真实的代理对象。 这是一个非常粗糙的例子,但足以让我们了解其背后的魔力。
请注意,如果希望您的方法调用通过,则需要在真实对象上调用该方法。 为此,您将需要后者的引用,而invoke
方法未提供该内容。 这就是为什么在大多数情况下,将其传递给构造函数并将其存储为属性的一个好主意。
在任何情况下都不应在代理本身上调用该方法,因为调用处理程序将再次拦截该方法,并且您将面临StackOverflowError。
要创建代理本身:
Listproxy=(List)Proxy.newProxyInstance(
NoOpAddInvocationHandlerTest.class.getClassLoader(),
newClass[]{List.class},
newNoOpAddInvocationHandler(list));
newProxyInstance
方法采用3个参数:
- 类加载器
- 代理将实现的接口数组
- 调用处理程序形式的王位背后的权力
现在,如果您尝试通过调用任何添加方法将元素添加到代理中,则它将没有任何效果。
CGLib代理
Java代理是接口的运行时实现。 对象不一定实现接口,并且对象的集合不一定共享相同的接口。 面对这样的需求,Java代理无法提供答案。
从这里开始CGLib的领域。 CGlib是基于ASM提供的字节码操作的第三方框架,可以帮助解决先前的限制。 首先提个建议,CGLib的文档与其功能不符:没有教程也没有文档。 您可以指望一些JavaDocs。 这表示CGLib放弃了纯Java代理所实施的许多限制:
- 您不需要实现接口
- 你可以延长课程
例如,由于Hibernate实体是POJO,因此Java代理无法用于延迟加载; CGLib代理可以。
纯Java代理和CGLib代理之间存在匹配:使用Proxy
地方使用net.sf.cglib.proxy.Enhancer
类,使用InvocationHandler
地方使用net.sf.cglib.proxy.Callback
。 两个主要区别是Enhancer
具有公共构造函数,并且不能仅通过其子接口之一使用Callback
:
-
Dispatcher
:分派增强器回调 -
FixedValue
:增强器回调,仅返回要从代理方法返回的值 -
LazyLoader
:延迟加载增强器回调 -
MethodInterceptor
:通用增强器回调,提供“周围建议” -
NoOp
:使用此Enhancer回调的方法将直接委托给基类中的默认(超级)实现
作为一个介绍性示例,让我们创建一个代理,无论后面的真实对象是什么,该代理都为哈希码返回相同的值。 该功能看起来像MethodInterceptor
,所以让我们这样实现它:
publicclassHashCodeAlwaysZeroMethodInterceptorimplementsMethodInterceptor{
publicObjectintercept(Objectobject,Methodmethod,Object[]args,
MethodProxymethodProxy)throwsThrowable{
if("hashCode".equals(method.getName())){
return0;
}
returnmethodProxy.invokeSuper(object,args);
}
}
看起来非常类似于Java调用处理程序,不是吗? 现在,为了创建代理本身:
Objectproxy=Enhancer.create(
Object.class,
newHashCodeAlwaysZeroMethodInterceptor());
同样,代理创建也不令人惊讶。 真正的区别是:
- 在此过程中不涉及任何界面
- 代理创建过程还会创建代理对象。 从呼叫者的角度来看,代理和代理之间没有明确的界限
- 因此,回调方法可以提供代理对象,而无需在您自己的代码中创建和存储它