UML软件工程组织

通用动态代理链-为你的应用程序添加AOP
作者:Matrix 来源:Matrix

摘要:

 大多数开发者都知道在运行时中如何装饰一个对象并向其增加额外的功能。四人帮(GoF)的装饰器模式可帮助开发者获得这个功能。在反射包中,J2SE 1.3引入动态代理用于动态地装饰一个业务对象。此外,链化动态代理可以在运行时动态地向一个业务对象增加多种行为。特别地,这些额外的行为的类型是由面向方面编程定位的。这篇文章并不打算对AOP作深入的讨论,而准备把重点放在动态代理的一般链化上,从而使开发者可以用以框架驱动的方法去实现一些AOP概念。如果一个项目早已使用用某些现存的AOP框架,那么开发者不用再担心实现一个定制的框架。开发者不论何种原因,在他们的项目中不使用这个框架仍然可以以一种有效的方法中且用较小的努力就可获得链化动态代理的好处。

今天,用简单Java对像(POJOs)编程是相当流行的。当我们用POJOs编程时,人们可以相当容易地应用面向对象编程方法(OOP)。但有时在一个项目中用OOP去实现横切面(cross-cutting aspects)被证明是很困难的。例如,通常地,对于某个项目,在一个POJOs的业务对象中用OOP去实现日志或安全功能是很因难的。在J2SE1.3中引入的动态代理提供了一种方便的解决方法。

动态代理的背后思想是在一个对象的周围插入动态的行为但不改变对象的已有代码和接口。著名的四人邦的装饰器模式提供一种不必改变对象代码就可以装饰一个对象(改变它的行为)并向其增加横切面的方法。现在的许多框架和工具都使用这个模式。但当实现静态的装饰时会导致一些问题,在这篇文章的后面我会对此进行讨论。

之前引入的动态代理是没有直接的办法用于动态地装饰一个对象的。于是供应商们提供一些会自动产生代码去装饰对象的工具。尽管代码生成工具可以帮助我们产生静态的装饰器,但它要求一些额外的步骤,同时还带来对生产代码的维护开销。通过使用动态代理,我们可以大大的减少自动生成的代码量(甚至可能是零)。

为了理解动态代理是如何工作的,同时看一下动态代理在它的位置上能起什么作用。让我们举一个装饰器类将作为一个方法拦截器的例子。如果像这样使用动态代理,我们可能就面临某些编程的复杂性。在这篇文章的后面,你将会看到如何包装和动态代理有关的复杂性,并在它们之上提供一个抽象,在这篇文章中所在使用的大多数源代码都可以从Resources下载。

版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
 作者:Srijeeb Roy;EsunYang(作者的blog:http://blog.matrix.org.cn/page/EsunYang)
 原文:http://www.matrix.org.cn/resource/article/44/44408_Dynamic+Proxy+AOP.html
 关键字:Dynamic;AOP;Proxy

没有动态代理的静态装饰和链化

假设我们有一个简单的业务接口:

public interface IMyBusinessObject { public String doExecute(String in);}

这个接口有一个业务对象类的实现。

public class MyBusinessObject implements IMyBusinessObject
{ public String doExecute(String in)
 { System.out.println("Here in MyBusinessObject doExecute: input :" + in);
  return in; }}

现在,我们想在doExecute()方法之前和之后增加一些行为(如logging)。装饰器模式使我们十分容易增加这个功能。

public abstract class ADecorator implements IMyBusinessObject
{ protected IMyBusinessObject target; public void setTarget(IMyBusinessObject target_)
  { this.target = target_; }
public ADecorator(){}
public ADecorator(IMyBusinessObject target_)
  { setTarget(target_);
}
}

现在定义一个从ADedorator扩展来的具体类DebugConcreteDecorator,我们的目的是在我们业务对象被调用之前和之后加入一些调试信息:

public class DebugConcreteDecorator extends ADecorator
 { public String doExecute(String in)
 { System.out.println("DebugConcreteDecorator: before method : doExecute ");
   String ret = target.doExecute(in);
   System.out.println("DebugConcreteDecorator: after method : doExecute ");
   return ret;
 }
 }

现在在客户端,我们调用业务对象:

IMyBusinessObject aIMyBusinessObject = new MyBusinessObject();
IMyBusinessObject wrappedObject = new DebugConcreteDecorator(aIMyBusinessObject);
wrappedObject.doExecute("Hello World");

在上面的代码片段中,我们用DebugConcreteDecorator类实例包装了我们的业务对象。因为DebugConcreteDecorator是从ADecorator类中扩展来的,并且ADecorator实现了业务对象接口IMyBusinessObject,因此DebugConcreteDecorator类本身就是接口IMyBusinessObject的实例。首先,我们创建一个MyBusinessObject实例,然后,我再创建DebugConcreteDecorator实例并在构造函数中向其传递业务对象。因为在DebugConcreteDecorator中是没有构造函数,所以它的超类(ADecorator)的构造函数会被调用,并目标对象设定为MyBusinessObject的实例。于是,当DebugConcreteDecorator实例的doExectute()被调用时,它首先记录一些调式信息(使用System.out)然后调用实际业务对象上(MyBusinessObject)的业务方法。
上面的调用输出如下:

DebugConcreteDecorator: before method : doExecute
Here in MyBusinessObject doExecute: input :Hello World
DebugConcreteDecorator: after method : doExecute

从输出中,我们可以看出,我们可以在业务方法被调用之前和之后加入调试信息。
我们还可以在实际的业务方法调用之前链化装饰器----调用一个装饰器后再调用另一个装饰器。让我们再定义另一个装饰器去展示这个方法:

public class AnotherConcreteDecorator extends ADecorator
 { public String doExecute(String in)
 { System.out.println("AnotherConcreteDecorator: Going to execute method : doExecute");
  in = in + " Modified by AnotherConcreteDecorator";
  String ret = target.doExecute(in);
  System.out.println("AnotherConcreteDecorator:
  After execute method : doExecute");
  return ret;
 }
 }

上面的代码片段通过在业务方法输入的字符参数后增加(" Modified by AnotherConcreteDecorator")字符串,从而实现对其进行了修改。
如果我们想链化装饰器,在客户端,我们可以编写如下的代码:

IMyBusinessObject aIMyBusinessObject = new MyBusinessObject();
IMyBusinessObject wrappedObject = new AnotherConcreteDecorator (newDebugConcreteDecorator(aIMyBusinessObject));
wrappedObject.doExecute("Hello World");

在上面的代码片段中,我在创建一个DebugConcreteDecorator实例时,向其传递了一个实际的业务对象实例。然后用一个刚才定义的AnotherConcreteDecorator实例去包装DebugConcreteDecorator实例。AntherConcreteDecorator首先在输入参数后增加字符串对其进行修改,然后调用DebugConcreteDecorator实例的doExecute()方法。这时,DebugConcreteDecorator会记录doExectute()方法的输出的条目,然后调用doExecute()去调用实际业务对象的doExecute()方法。

它的返回路径以相反的顺序。在从实际的业务对象(MyBusinessObject)doExecute()方法返回之后,DebugConcreteDecorator余下代码将被执行。于是,调用返回至AnotherConcreteDecorator实例并执行余下的部份代码。
上面的调用产生如个输出:

AnotherConcreteDecorator: Going to execute method : doExecute
DebugConcreteDecorator: before method : doExecute
Here in MyBusinessObject doExecute: input :Hello World Modified by AnotherConcreteDecorator
DebugConcreteDecorator: after method : doExecute
AnotherConcreteDecorator: After execute method : doExecute

上面方法所展示的类图如图1:


 图1。用静态装饰器修饰的业务对象类图

让我们看一下静态装饰器所产生的问题。

考察DebugConcreteDecorator 或 AnotherConcreteDecorator中doExecute()方法。它对目标对象doExecute()方法的调用进行了硬编码。并且,如果我们在IMyBusinessObject接口中定义另一个方法,我们必须改写所有装饰器并提供这个方法的实现。于是,在实践中,我们可能会因有许多装饰器和在每个装饰器中有大量的代码而止步。一个动态代理可以帮助我们去掉这些硬编码,此外,我们不必在每个装饰器去实现和改写业务接口中的每个方法。

在这篇文章中我不打算深究动态代理的细节。相反,我会举一个小例子,同时展示动态代理如何工作的。然后,我直接进入动态代理的链化从而以通用的方法去拦截方法的调用。

J2SE 1.3动态代理:一个例子

动态代理类是一个在运行时所指定的一列接口的实现。动态代理接口是一种由代理类实现的接口,并且是一个java.lang.reflect.Proxy类的实例。每一个代理实例都与一个调用处理器对像相联,这个调用处理器实现了java.lang.reflect.InvocationHandler接口。在代理实例上的一个方法调用是通过其中之一的代理接口被转发到与这个代理实例相联的调用处理的invoke方法上。一个java.lang.reflect.Method对象会决定那一个方法会被调用,一个类型为java.lang.Object的数组包含调用的参数。调用处理器会适当地解码方法的调用(encoded method invocation as appropriate),并且它(调用处理器)的返回结果被作为在代理实例上方法调用返回的结果而返回。

例如,我们已有和前面例子一样的接口IMyBusinessObject和业务类MyBusinessObject。现在当我们使用动态代理时,我必须编写一个调用处理器,因为java.lang.reflect.Proxy类将会用到它。

DebugInvocationHandler类看起来像下面那样:

public class MyDebugInvocationHandler implements java.lang.reflect.InvocationHandler
{ private Object target = null; public void setTarget(Object target_)
 { this.target = target_; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
 { try { System.out.println("Going to execute method : " + method.getName); Object retObject =   method.invoke(target, args);
System.out.println("After execute method : " + method.getName()); return retObject; } catch(InvocationTargetException e)
{ throw e.getTargetException(); }
catch(Exception e)
{ throw e; }
}}

在上面的例子中,invoke ()方法是很重要的,它是被java.lang.reflect.Proxy类所调用的。在这个方法里面,我们执行了一些额外的处理,然后转至真正的目标对象的处理(在这个例子中,是实例MyBusinessObject)。
因此我们的客户端应作如下编码:

IMyBusinessObject bo = new MyBusinessObject(); 
MyDebugInvocationHandler aMyDebugInvocationHandler = new MyDebugInvocationHandler();
aMyDebugInvocationHandler.setTarget(bo);IMyBusinessObject proxyObject = (IMyBusinessObject) Proxy.newProxyInstance (IMyBusinessObject.class.getClassLoader(), new Class[] { IMyBusinessObject.class }, aMyDebugInvocationHandler);
System.out.println(proxyObject.doExecute("Hello World"));

在上面的代码中,我分别创建一个MyBusinessObject实例和一个MyDebugInvocationHandler实例。我们在MyDebugInvocationHandler中设定目标对象为MyBusinessObject。因此当invoke()方法被调用时,它能够把调求发向正确的目标。然后,我们使用java.lang.reflect.Proxy去创建一个IMyBusinessObject接口的代理对象。既然invoke()方法会处理java.lang.reflect.Mehtod类的生成并且其中没有特定的业务接口的方法,通常这些特定的业务接口的方法在每一个业务接口分别编写个业调用处理器是必须的,知道这一点是很重要的。还有,如果我们想实现一些横跨所有业务接口的横切面(cross-cutting aspect),我们不必实现在业务接口中定义的所有业务方法。例如,为了在我们的业务方法中实现安全性,我们仅仅只须在一个地方编写一个方法去实现安全逻辑,这个安全方法我们将以通用的方法编写。

如果我们想在链中增加多个处理器,我们必须创建另一个调用处理器。然后在新定义的处理器中的setTarget()中我们把它设定为链中的前一个代理对象,而不是设定为MyBusinessObject对象。因此代码看起来像下面那样子:

MyAnotherInvocationHandler aMyAnotherInvocationHandler = new MyAnotherInvocationHandler ();
//Here we will set the proxyObject, which we get through earlier //code snippet, instead of the business object instanceaMyAnotherInvocationHandler.setTarget(proxyObject);IMyBusinessObject nextProxyObject = (IMyBusinessObject) Proxy.newProxyInstance (IMyBusinessObject.class.getClassLoader(), new Class[] { IMyBusinessObject.class }, aMyAnotherInvocationHandler);System.out.println(nextProxyObject.doExecute("Hello World"));

从上面的例子我们可以看出,如何用动态代理以比静态装饰链更少的代码为业务对象增加额外的行为。但是,如果我们像上面一样使用动态代理仍然有一些问题:当创建和链化动态代理时你仍然必须编写大量的代码,并且你还必须处理不如普通的对象创建或工厂方法的对象创建那么友善的代理接口API,还有,当我们需要代理我们的业务对象时,在多个位置重复一些代码并不是一个好主意。
文章的余下部份将会试图去解决这些问题。我将会编写一个暴露简单API的通用代理工厂(在其中隐藏代理对象的创建和链化),但仍可提供动态代理的扩展性。

通用链化动态代理:方法一

如果我们的用户使用下面的代码片段去调用业务对象合适吗?

String[] interceptorClasses = {"MyDebugInterceptor", "MyAnotherInterceptor"};
IMyBusinessObject aIMyBusinessObject = (IMyBusinessObject)MyProxyFactory.getProxyObject ("MyBusinessObject", interceptorClasses);
String ret = aIMyBusinessObject.doExecute("Hello World");

上面的代码的目的是,提供存储一个拦截器类名的java.lang.String 数组和在MyProxyFactory类的方法内提供业务类。我们期望MyProxyFactory可以创建业务对象,并用在getProxyObject()方法上的第二个参数所传递来的拦截器包装它。现在如果我们调用doExectue()业务方法,所有在链上的拦截器都会执行,并且实际的业务方法最终也会被调用到。

如果MyDebugInterceptor 和 MyAnotherInterceptor是通用的并且不随我们的业务接口(IMyBusinessObject)而变化的话,这也是合适的。为达到这种功能,我们现在检查下面的步骤。

因为这个方法,我们假定在业务方法被执行之前和之后方法的拦截都会独立的完成。我们为拦截器定义一个接口:

public interface IMethodInterceptor { Object interceptBefore(Object proxy, Method method, Object[] args, Object realtarget);
void interceptAfter(Object proxy, Method method, Object[] args, Object realtarget, Object retObject, Object interceptBeforeReturnObject);}

我们的目的是在执行任何业务方法之前调用interceptBefore()方法,在成功执行我们的业务方法之后调用interceptAfter()方法(没有抛出任何异常)。

现在我们写两个IMethodInterceptor接口的实现:

public class MyDebugInterceptor implements IMethodInterceptor { public Object interceptBefore(Object proxy, Method method, Object[] args, Object realtarget)
{ System.out.println ("MyDebugInterceptor: Going to execute method : ");
return null; } public void interceptAfter (Object proxy, Method method, Object[] args, Object realtarget, Object retObject, Object interceptBefore) { System.out.println ("MyDebugInterceptor: After execute method : " ); }}

这是第一个实现,在方法被转至目标对象object.interceptBefore()之前,MyDebugInterceptor's interceptBefore()方法拦截请求-----仅仅是向控制台打印一行。

public class MyAnotherInterceptor implements IMethodInterceptor { public Object interceptBefore(Object proxy, Method method, Object[] args, Object realtarget) { System.out.println("MyAnotherInterceptor: Going to execute method : ");
if ( method.getName().equals("doExecute") && args != null && args.length >= 1 ) { if ( args[0] instanceof String ) { args[0] = args[0] + " Modified by MyAnotherInterceptor"; }
return null; } }
public void interceptAfter(Object proxy, Method method, Object[] args, Object realtarget, Object retObject, Object interceptBefore)
{ System.out.println("MyAnotherInterceptor: After execute method : ");
}}

上面是第二个实现,在方法被转至目标对象之前,MyAnotherInterceptor's interceptBefore()方法也拦截请求。这时,它修改请求参数并改变它的值(如果方法名称是doExecute()的话 ),它输入的字符串参数中附加了“Modified by MyAnotherInterceptor”字符串。
下一步是写一个能处理我们刚才所写的拦截器的通用调用处理器。

public class GenericInvocationHandler implements java.lang.reflect.InvocationHandler { private Object target = null; public void setTarget(Object target_)
{ this.target = target_; }
private Object realtarget = null;
public void setRealTarget(Object realtarget_)
{ this.realtarget = realtarget_; }
IMethodInterceptor methodInterceptor = null;
public void setMethodInterceptor (IMethodInterceptor methodInterceptor_)
{ this.methodInterceptor = methodInterceptor_; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{ try { Object interceptBeforeReturnObject = null;
if ( methodInterceptor != null )
{ interceptBeforeReturnObject = methodInterceptor.interceptBefore (proxy, method, args, realtarget ); }
Object retObject = method.invoke(target, args);
if ( methodInterceptor != null )
{ methodInterceptor.interceptAfter (proxy, method, args, realtarget, retObject, interceptBeforeReturnObject ); }
return retObject; }
catch(InvocationTargetException e)
{ throw e.getTargetException(); }
catch(Exception e)
{ throw e; } }}

请看一下在IMethodInterceptor 上的interceptBefore() 和 interceptAfter()方法在方法被调用之前和之后是如何被分别地插入的。
下一步是写一个MyProxyFactory类,它可通用地创建代理并链化他们。这特别的一步应该可以给你一个如何使用动态代理设计自己框架的印象。

public class MyProxyFactory { public static Object getProxyObject( String className, String[] interceptors ) throws Throwable { Object inputObject = getTargetObject(className);
if ( interceptors != null && interceptors.length > 0 ) { Object inputProxiedObject = inputObject; for ( int i=0; i < interceptors.length; i++ )
{ inputProxiedObject = getProxyObject(inputObject, interceptors[i], inputProxiedObject); }
return inputProxiedObject; }
else { return inputObject; } }
private static Object getProxyObject(Object inObject, String interceptor,Object inProxiedObject) throws Throwable
{ GenericInvocationHandler invocationHandler = new GenericInvocationHandler(); IMethodInterceptor interceptorObject = (IMethodInterceptor)getInterceptor(interceptor);
if ( interceptor == null ) { return inProxiedObject; } invocationHandler.setTarget(inProxiedObject);
invocationHandler.setRealTarget(inObject); invocationHandler.setMethodInterceptor(interceptorObject);
return Proxy.newProxyInstance (inObject.getClass().getClassLoader(), inObject.getClass().getInterfaces(), invocationHandler) ; }
private static Object getInterceptor( String interceptors ) throws Exception { //... //From the class name return the class instance. //You can use Class.forName and newInstance() method on Class //to return the Object instance. } private static Object getTargetObject( String className ) throws Exception { //... //From the class name return the class instance. //You can use Class.forName and newInstance() method on Class //to return the Object instance. }}

这段代码,code's public static Object getProxyObject(String className, String[] interceptors)方法里,所有被遍历的拦截器迭代器会调用每个拦截器的private static getProxyObject方法,它的每次调用都会创建一个GenericInvocationHandler实例。这个方法还会设定目标对象和GenericInvocationHandler实例的拦截器,并且在代理类的static newProxyInstance()方法传递这个实例。

当客户调用public static getProxyObject方法时,对于第一个迭代器,它用下面三个参数调用private getProxyObject()方法:
 1. 业务对象实例
 2. 用java.lang.String的表示第一个拦截器的名称。
 3. 与第一个业务对象相同的一个实例。

一个新的代理对象会用在代理链上的第一个拦截器去包装业务对象实例,并且返回一个代理对象。
 在第二个迭代器中,public static getProxyObject方法用下面三个参数调用private getProxyObject():
 1业务对象实例
 2用java.lang.String的表示第二个拦截器的名称。
 3用第一个拦截器包装的业务对象的实例的代理对象。

一个新的代理对象会使用在拦截链上的第二个拦截器去包装第一个代理对象(从循环中的第一个迭代器返回),并且返回一个新创建的代理对象。

你可能会有下面的想法:通用链化是发生在MyProxyFactory类中。同时知道这一点也很重要:当产生许多的拦截器时,也会同时产生许多的GenericInvocationHandlers和代理实例(因为它们都是在同一个循环中创建的)。

在这个例子中,类的名称是用java.lang.String传送的并且用到了Class.forName()方法,此外,newInstance()方法被用于去实例化目标对象及迭代器。在真正的实践中,每次都使用Class.forName 和 newInstance()方法可能会导致问题。我们可能想在我们的业务对象或拦截器被调用之前设置某些变量,或者我们想某些业务对象和拦截器被定义为Singleton实例。在一个实际的实践中这些详细的细节必须要考虑的,在方法三中,你将会看到这些特殊性如何被获得以达到某些扩展性。

方法一的程序结果与我们的静态装饰器结果是相匹配的。但这里我获得更好的扩展性。既然MyProxyFactory 和 GenericInvocationHandler是通用的,我们可以使用它去包装任何对象。当然,我们的业务对象必须实现某些预定义的接口,这是一个良好的编程实践。

MyDebugInterceptor 和 MyAnotherInterceptor也可以被通用化而不必为每一个业务接口和业务方法去分别编写代码。假设我们想在业务方法执行之前做一些安全业务方法检查,我们可以在一个拦截器里面的interceptBefore()方法实现。这个安全拦截器可读取文件/数据库,然后执行安全检查。例如我们有下面自定义的安全XML文件:

MyBusinessObject doExecute admin,manager

这个配置可以被读取并缓存起来,之后,安全拦截器会根据方法名称进行处理并用角色来匹配它。(注意:你可能觉得奇怪,在拦截中,我们是如何获得用户的角色的,因为我们并没有传递角色过来。原来它们是用TreadLocal处理的,但这是另一个主题)。
于是,在安全拦截器interceptBefore()方法里面,代码类似如下片段:

public Object interceptBefore(Object proxy, Method method, Object[] args, Object realtarget) { String nameOfMethod = method.getName(); String targetClassName = realtarget.getClass().getName(); MethodPermissions mPerm = SecurityFactory.getPermission(targetClassName); If ( !mPerm.isAuthorized(MyThreadLocalRoleStore.getRole(), nameOfMethod ) ) { throw new RuntimeException("User not authorized"); } return null;}

我们还必须编写MethodPermissions, SecurityFactory, MyThreadLocalRoleStore去实现完全流动(overall flow),但这些扩展已超出的讨论的范围。并且列出XML仅仅是为了举例而已。对于真正的应用,则需要更健壮的代码(例如异常处理等)。如果在我们业务对象中有一个过载的方法会发生什么事情呢?XML和interceptBefore()方法内的代码都需要改变,并且诸如MethodPermissions和 SecurityFactory也需要处理那些细节性的改变。
方法一的类图结构如下:


 图2。链化动态代理:方法一类图

注意:因为了简化及篇幅的限制,在图2至图4某些类的方法和参数以及接口的细节都没出画出来。在上面的类图中,注意到业务接口IMyBusinessObject并不一定要与任何其它类或接口相联,但除了业务对象之外。

动态链化动态代理:方法二

方法二处理与方法一有相同的类集,并且在客户端的角度来睇也是一样的。可能要改变的是GenericInvocationHandler和 MyProxyFactory。正如方法所说,因为大量的拦截器被创建,GenericInvocationHandler实例和代理实例也大量增加。方法二试图去解决这个问题并同时获得与方法一相近的好处。

因此,客户端的代码与方法一相同:

String[] interceptorClasses = {"MyDebugInterceptor", "MyAnotherInterceptor"};IMyBusinessObject aIMyBusinessObject = (IMyBusinessObject)MyProxyFactory.getProxyObject ("MyBusinessObject", interceptorClasses);String ret = aIMyBusinessObject.doExecute("Hello World");

在这个方法中我们的目的是仅仅使用一个GenericInvocationHandler实例和代理实例。让我们看一下新的
GenericInvocationHandler代码:

public class GenericInvocationHandler implements java.lang.reflect.InvocationHandler { private Object realtarget = null;
public void setRealTarget(Object realtarget_) { this.realtarget = realtarget_; } Object[] methodInterceptors = null; public void setMethodInterceptors (Object[] methodInterceptors_) { this.methodInterceptors = methodInterceptors_; }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Object[] retInterceptBefore = null; if ( methodInterceptors != null && methodInterceptors.length > 0 ) { retInterceptBefore = new Object[methodInterceptors.length];
for ( int i= methodInterceptors.length - 1; i >= 0; i-- ) { if ( methodInterceptors[i] != null ) { retInterceptBefore[i] = ((IMethodInterceptor)methodInterceptors[i]). interceptBefore(proxy, method,args, realtarget ); } } }
Object retObject = method.invoke(realtarget, args);
if ( methodInterceptors != null )
{ for ( int i= 0; i < methodInterceptors.length; i++ )
{ if ( methodInterceptors[i] != null )
{ ((IMethodInterceptor)methodInterceptors[i]).
interceptAfter(proxy, method, args, realtarget, retObject, retInterceptBefore[i] );
}
}
}
return retObject; }
catch(InvocationTargetException e)
{ throw e.getTargetException(); }
catch(Exception e)
{ throw e; }
}
}

GenericInvocationHandler在调用业务接口上的方法之前会处理一个方法拦截器的数组。它会迭代数组中所有的解释器,并调用每一个解释器的interceptBefore()方法。类似地,在业务方法调用之后,GenericInvocationHandler也会在循环中调用interceptAfter()方法。请注意:interceptBefore()在循环中以相反的方向调用的而interceptAfter()是正向调用的。

MyProxyFactory类也需要改变,但getInterceptor() 和 getTargetObject()则与原来一样。新的MyProxyFactory代码如下:

public static Object getProxyObject( String className, String[] interceptors ) throws Throwable { Object inputObject = getTargetObject(className);
if ( interceptors != null && interceptors.length > 0 )
{ return getProxyObject(inputObject, interceptors); }
else { return inputObject; }}
private static Object getProxyObject(Object inObject, String[] interceptors) throws Throwable { GenericInvocationHandler invocationHandler = new GenericInvocationHandler();
Object[] interceptorObjects = getInterceptors(interceptors); invocationHandler.setRealTarget(inObject); invocationHandler.setMethodInterceptors(interceptorObjects);
return Proxy.newProxyInstance (inObject.getClass().getClassLoader(), inObject.getClass().getInterfaces(), invocationHandler) ;}
private static Object[] getInterceptors(String[] interceptors) throws Exception { Object[] objInterceptors = new Object[interceptors.length];
for ( int i=0; i < interceptors.length; i++ )
{ objInterceptors[i] = getInterceptor(interceptors[i]); }
return objInterceptors;}

从上面的代码我们可以看出,GenericInvocationHandler仅有一个实例。此外,仅只有一个代理对被创建并且不随许许多多的拦截器而变化。因此,它需要更少的内存。还有,方法二比方法一还会稍微快一些。在最后我将会用一个简单的测试去对比所有方法的结果。

某些人可能会指出,在方法二我们仅仅在GenericInvocationHandler中而不在MyProxyFactory中循环所有的拦截器。需要知道的重点是:在方法一和方法二,拦截器的实例数量是一样的。在方法一中,在循环中还额外地创建了GenericInvocationHandlerS和代理对象。但在方法二中,仅仅只有一个GenericInvocationHandler实例和代理被创建。
方法二的类图如图形所示:


 图3.通用链化动态代理:方法二类图

在IMethodInterceptor接口增加一个能够拦截由目标对象抛出的异常的方法(例如:interceptException()),方法一和方法二可能会更有用(GenericInvocationHandler也需要修改)。此外,我们可以为全部的三个方法((interceptBefore(), interceptAfter(), 和 interceptException())创建不同的接口。我建议你把接口拆分为不同的小接口。例如,我们仅仅想在业务对象执行之前进行拦截,那么我们可能使用仅仅含有interceptBefore()方法的接口而不用考虑关于其它方法的实现。在面向方面编程中(AOP),拦截是作为程序的不同的部份,称为通知(advice)。于是在通知(advice)之前(相对于interceptBefore()方法),在返回通知(advice)之后(相对于interceptAfter()方法),或抛出通知(相对于interceptException ()方法),分别创建各种接口,。另一种更通用更具扩展性的通知能够一齐处理所有的方法的拦截。但既然它是更通用的方法,开发者可能要做更多的工作(开发者要定位下一个目标的方法)。这种通知类型称为环绕通知(around advice)。后面将讨论的方法三将会与AOP的环绕通知(around advice)作一个对比。方法一和方法二都有局限性,不能充分利用不必作大修改的动态代理的优点。例如,既然interceptBefore() 和 interceptAfter()不是直接被调用的,那么在interceptBefore() 和 interceptAfter()之间插入某些控制将会变得很困难。比如我们想写一个同步(synchronized)业务对象的拦截器,我们想达到下面的样子:
synchronized(realtarget) { retObject = method.invoke(target, args);}

在方法三中,我将马上开始学习。

动态链化动态代理:方法三

在方法三中,客户代码类似于方法一和方法二:

String[] invocationHandlers = {"MyDebugInvocationHandler", "MyAnotherInvocationHandler"};
IMyBusinessObject aIMyBusinessObject = (IMyBusinessObject)MyProxyFactory.getProxyObject ("MyBusinessObject", invocationHandlers);String ret = aIMyBusinessObject.doExecute("Hello World");

在方法三中,我们对IMyInvocationHandler作如下的定义:

public interface IMyInvocationHandler { void setTarget(Object target_); void setRealTarget(Object realtarget_);}

我们还定义一个实现IMyInvocationHandler 和 java.lang.reflect.InvocationHandler接口的抽象方法:

public abstract class AMyInvocationHandler implements IMyInvocationHandler, java.lang.reflect.InvocationHandler { protected Object target = null;
protected Object realtarget = null;
public void setTarget(Object target_) { this.target = target_; }
public void setRealTarget(Object realtarget_) { this.realtarget = realtarget_; }}

在方法三中并没有包含 GenericInvocationHandler类或 IMethodInterceptor接口。相反,我们的拦截器扩展了AMyInvocationHandler类。让我们看一下我们的两个拦截器,它们现在提供对接口java.lang.reflect.InvocationHandler中invoke()方法的实现:

1. public class MyDebugInvocationHandler extends AMyInvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { System.out.println("MyDebugInterceptor: Before execute method : " + method.getName());
Object retObject = method.invoke(target, args);
System.out.println("MyDebugInterceptor: After execute method : " + method.getName());
return retObject; }
catch(InvocationTargetException e)
 { throw e.getTargetException(); }
catch(Exception e)
 { throw e; }
}
}

2. public class MyAnotherInvocationHandler extends AMyInvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { System.out.println("MyAnotherInvocationHandler: Before execute method : " + method.getName()); if ( method.getName().equals("doExecute") && args != null && args.length >= 1 ) { if ( args[0] instanceof String ) { args[0] = args[0] + " Modified by MyAnotherInvocationHandler"; } } Object retObject = method.invoke(target, args);
System.out.println("MyAnotherInvocationHandler: After execute method : " + method.getName());
return retObject; } catch(InvocationTargetException e) { throw e.getTargetException(); } catch(Exception e) { throw e; } }}

在类MyDebugInvocationHandler 和 MyAnotherInvocationHandler中,在拦截doExecute()之前或之后并没有明显的接口或接口方法可用或抛出任何异常去匹配方法调用。考虑Servlet的Filter接口,里面仅有一个doFilter()方法,而没有doBefore()或doAfter()方法。方法三有与之类似的功能。

 方法三提供了比其它方法(方法一和方法二)更好的扩展性。例如,通过方法三,看一下我们是如何获得同步性的。我们仅仅实现了从AMyInvocationHandler扩展的同步处理器并在invoke()方法内实现同步逻辑。为了测试它的同步行为,我们要确保有多个线程同时地去访问相同业务实例上的doExecute()方法。同步处理器的例子如下:

public class MySynchronizeInvocationHandler extends AMyInvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object retObject = null; synchronized(realtarget) { retObject = method.invoke(target, args); } return retObject; }}

让我们看一下方法三的MyProxyFactory主代码:

public static Object getProxyObject( String className, String[] invocationHandlers ) throws Throwable { Object inputObject = getTargetObject(className);
if ( invocationHandlers != null && invocationHandlers.length > 0 )
{ Object inputProxiedObject = inputObject;
for ( int i=0; i < invocationHandlers.length; i++ )
{ AMyInvocationHandler myInvocationHandler = (AMyInvocationHandler)getInvocationHandler (invocationHandlers[i]);
inputProxiedObject = getProxyObject(inputObject, myInvocationHandler, inputProxiedObject); }
return inputProxiedObject; }
else
{ return inputObject;
}
}
public static Object getProxyObject( Object inputObject, Object[] invocationHandlers ) throws Throwable { if ( invocationHandlers != null && invocationHandlers.length > 0 )
{ Object inputProxiedObject = inputObject;
for ( int i=0; i < invocationHandlers.length; i++ )
{ inputProxiedObject = getProxyObject(inputObject, (AMyInvocationHandler)invocationHandlers[i], inputProxiedObject); }
return inputProxiedObject; }
else { return inputObject; }}
private static Object getProxyObject(Object inObject, AMyInvocationHandler myInvocationHandler, Object inProxiedObject) throws Throwable
{ if ( myInvocationHandler == null ) { return inProxiedObject; }
myInvocationHandler.setTarget(inProxiedObject);
myInvocationHandler.setRealTarget(inObject);
return Proxy.newProxyInstance (inObject.getClass().getClassLoader(), inObject.getClass().getInterfaces(), myInvocationHandler) ;}

这段代码与方法一的MyProxyFactory几乎一样。这个类同样在一个循环里迭代调用处理器的数组并为每个调用处理器和代理对象创建实例。在方法一中,GenericInvocationHandler,拦截器和代理对象都是在一个循环中被创建的。在方法三中,仅调用处理器和代理对象在一个循环中被创建的(因为这里面没有独立分开的拦截器)。

在上面的代码片段,我还要介绍一下别一个在MyProxyFactory类中public static方法,它定义如下:

public static Object getProxyObject ( Object inputObject, Object[] invocationHandlers )

这就像前面我所提到用Class.forName或某些类似的含意。在目标对象,在调用处理器或在拦截器使用Class.forName方法之前,我们不能设定任何变量。因此,为了简化,我提供了上面的方法(你也可以在方法一和方法二中使用相同的办法)。现你可以传递一个标对象,调用处理器或拦截器的实例。例如,客户端的代码看起像下面的样子:

MyDebugInvocationHandler dHandler = new MyDebugInvocationHandler();
MyAnotherInvocationHandler aHandler = }new MyAnotherInvocationHandler();IMyBusinessObject bo = new MyBusinessObject();//Now set any variables to these objects//or some of these objects may be singletonsObject[] invocationHandlers = { dHandler, aHandler }; (IMyBusinessObject)MyProxyFactory.getProxyObject (bo , invocationHandlers);
String ret = aIMyBusinessObject.doExecute("Hello World");

方法三的类图如图4所示:


 图4通用链化动态代理:方法三类图

除了这篇文章所描述的方法之外,还存在其它各种不同的方法。特别地,代理工厂可以有完全不现的写法。而且你可能写一个混合或匹配方法一和方法三的代理工厂。

不同方法之间的简单比较

我会做一个简单的测试去看一看去调用一个在被拦截器包装之后实际的业务对象的每一个业务方法要花多少时间。因为没考虑机器配置的原因(比如处理器速度及数量,使用内存的大小等)这个测试不能作为定量的测试。但从这个测试中,我们仍然可以对每一个方法的效率获得大概的印象。

为了进行测试,首先,所有的System.out.println语句都应该注释掉(确保不受I/O时间因素影响)。然后,在每一个方法里面,在一个循环内使用代理工厂去调用的业务方法的调用都运行1000次。在它的顶层,每一个程序运行20次。下面的信息显示了1000次(连续的)调用平均毫秒时间。

 静态装饰链: 51.7 毫秒 每1,000 调用
 动态代理链:方法1: 166.5毫秒 每1,000 调用
 动态代理链:方法2: 125.1毫秒 每1,000 调用
 动态代理链:方法3: 159.25毫秒 每1,000 调用

尽管静态装饰器链化所需的时间少得多,如果我们考虑单一的方法调用,那么比较会进入微秒级,这就使得差异变得无关紧要。在实际应用中,为获得毫秒或微妙的时间而不使用通用的方法的通常结果是,在分散的代码中使用更多的客户代码,这实际上花费更多的执行时间。

就像前面所指的一样,我们可混合和匹配方法一和方法三去建立一个良好的AOP框架。另一方面,如果我们需要定义横切面(可以获得所有的好处)去组织程序,并且我们需要考虑到微秒级的性能,那么我们可以考虑使用方法二。

结论

在这篇文章的例子中,在工厂里的方法是静态的。如果你扩展一个工厂或想捅用配置不同的多个工厂实例,这种方法可能会引致以后才出现的问题。因此,要使用合适的工厂模式去编写工厂方法。

我在此仅仅接触了一些AOP概念,并展示在java中如何用动态代理去实现它。其它几个AOP概念如连接点(在程序执行中定义良好的点,例如一个方法调用或一个被抛出的特定异常)点切(point cuts)(一组连接点)都可以在你的工厂类中实现。请查看Resources以获得更详细的信息。

如果你需在你的应用程序中大量使用AOP概念并且可以选择一个现有的框架,那么你可以选择一个有名的,经过严格测试的和支持良好的框架,而不是自己建造一个。如果这样的一个框架对于你是可选的,那么可考虑建造一个你自己的框架。一个旨在提供用于拦截方法调用的java AOP接口的AOP联盟已成立(查看Resources以获得更多的信息)。在建造你自己的框架之前,请查看一下AOP联盟的接口。

如果你使用一个并不提供AOP支持的框架,但你又需要实现一些AOP特性,那么不要在项目中引入一成熟的AOP框架,请考虑用通用动态代理建立自己的小框架。十分希望这篇文章能为如何实现这个功能提供一些帮助。如果你使用一个使用动态代理方法的AOP框架,那么这篇文章应该可以帮助你理解动态代理和它们的链化的基本原理。

资源:
 代码下载:http://www.javaworld.com/javaworld/jw-01-2006/proxy/jw-0130-proxy.zip
 javaworld:http://www.javaworld.com
 Matrix:http://www.matrix.org.cn

 

版权所有:UML软件工程组织