电脑技术学习

动态委托及其应用

dn001
内容:
动态委托及其应用

作者:Lu Jian

11/17/2004

翻译:DannyTan


版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
作者:
Lu Jian; DannyTan[/center]
原文地址:
http://www.onjava.com/pub/a/onjava/2004/11/17/dunamis.html
中文地址:
http://www.matrix.org.cn/resource/article/43/43821_Dynamic_Delegation.html
关键词: Dynamic Delegation



代理模式是一个在面向对象编程中重要而广泛被使用的设计模式。JDK1.3中已经介绍了Proxy,你在Java使用过Proxy吗?动态代理类实现了在运行时所指定的一组接口。在运行时,通过使用InvocationHandler来提供代理的行为。因此,Proxy是在JAVA反射包中一个重要的类,并且广泛地用于许多JAVA应用程序中。

Proxy的一个限制是它仅仅接受接口。在某些情况下,你不仅需要使用代理模式来代理接口,而且也需要用它来代理抽象类,甚至具体的类。

本文介绍了动态委托(Dynamic Delegation),它能够创建运行时接口和类的代理。

Proxy概述

在JDK1.3中, java.lang.reflect包增加了Proxy类。它能够创建一个具体类,这个类实现了所有在运行时指定的所有接口。动态生成的类将所有定义在接口中的方法调用重定向到InvocationHandler。

给定两个接口,Idel1和Idel2, Proxy将创建一个IdelProxy类作为这两个接口的代理(为了方便起见,使用IdelProxy作为生成的代理类名字)。图1展现了这种结构:


图1. IdelProxy的类图

下面是相关的简要代码:

 Class clazz = Proxy.getProxyClass(
Idel1.class.getClassLoader(),
new Class[] { Idel1.class, Idel2.class });


委托与代理的比较

Proxy仅仅对接口进行代理。如果想要用Proxy进行接口和类的代理,我们需要做什么呢? java.net的Dunamis project项目介绍了另一种相应的模式—Delegation模式。Delegation使用了与Proxy不同的方法实现。

给定一个名字为TestBean的类,委托类TestBeanDelegation的类图如图2所示:


图2. TestBeanDelegation的类图

TestBeanDelegation实现了Delegation接口并且继承了TestBean类。它也包含了对TestBean和DelegationInvocationHandler的引用。所有TestBeanDelegation上的方法调用都是对这两者的委托。

以getName()为例,图3描述了方法调用的顺序图。


图3. TestBeanDelegation.getName()调用的顺序图

相关伪码如下:

//The delegation class is a sub-class of the class to be delegated
public class TestBeanDelegation extends TestBean
implements Delegation {
//The object to be delegated
TestBean bean;
//The invocation handler
DelegationInvocationHandler handler;
...
static Method m0 = null;
...

static {
...
try {
m0 = TestBean.class.getMethod("getName",
new Class[] {});
} catch (Exception exception) {
}
...
}

public TestBeanDelegation(Object bean) {
this.bean = (TestBean)bean;
}

public String getName() {
boolean goon = true;
String ret = null;
Throwable t = null;
try {
goon = handler.invokeBefore(bean,
m0, new Object[] {});
if (goon)
try {
ret = bean.getName();
} catch (Throwable throwable) {
t = throwable;
}
if (t != null)
ret = handler.invokeAfterException(bean,
m0, new Object[] {}, t);
else
ret = handler.invokeAfter(bean,
m0, new Object[] { name }, null);
return ret;
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}



动态委托的介绍

动态委托概念来自于Jakarta 字节码工程库 (Byte-Code Engineering Library, BCEL)。它能够分析存在的类,并且对于接口,抽象类,甚至运行时的具体类来说,它能够生成以字节编码委托类。

被委托的接口/类应该满足如下条件:
动态委托最多只能委托一个类,但是能够代理多个接口。
这个限制来自于Java的单继承模式。一个Java类最多只有一个父类。既然生成的委托类把被委托类作为它的父类,那么指定多个被委托类是不合理的。如果没有指定被委托类,那么缺省的父类就是Object。

被委托类应该有一个有限定符public或者protected的缺省构造函数。
委托类会在它自己的构造函数中调用父类的缺省构造函数。

被委托类不能是final,而应该对于它的调用者可见。
Proxy生成的代理类是final的。而动态委托却不能接受这种情况。

动态委托不能委托实现了接口Delegation的任何类。
既然类已经是一个委托类,就没有必要重新委托它。

生成的委托类有下面的特点:
委托类是在运行时产生的,没有类文件
委托类实现了所有继承的接口,扩展了被委托的类。
委托类也实现了Delegation接口。
委托类有一个接受Object实例作为参数的构造函数。

DelegationGenerator是动态委托的主要类。客户程序能够使用它来生成指定类,接口,对象的委托类和对象。DelegationInvocationHandler是一个定义了所有委托行为的接口,客户程序的开发人员应该实现这些所有的接口。委托对象能够使用定义在Delegation中的_getInvocationHandler和_setInvocationHandler方法来访问委托对象中的DelegationInvocationHandler实例。

练习1。创建一个具体类的委托类

假定存在一个名字为ConcreteClass的具体类:


//ConcreteClass.java
package org.jingle.util.dydelegation.sample;

public class ConcreteClass {
public void hello() {
System.out.println("Hello from ConcreteClass");
}

protected void hello2() {
System.out.println("Hello again from ConcreteClass");
}
}


下面的代码生成ConcreteClass类的委托类

//ConcreteClassTest.java
package org.jingle.util.dydelegation.sample;

import org.jingle.util.dydelegation.DelegationGenerator;

public class ConcreteClassTest {
public static void main(String[] args) {
Class clazz = DelegationGenerator
.getDelegationClass(new Class[] { ConcreteClass.class });
System.out.println("Delegation class name = " +
clazz.getName());
System.out.println(
ConcreteClass.class.isAssignableFrom(clazz));
}
}


输出为:

Delegation class name =
org.jingle.util.dydelegation.sample.ConcreteClass_Delegation_0
true

DelegationGenerator.getDelegationClass()接受类数组为参数,返回一个Java类,这个类继承了给定类或者实现了给定接口。缺省情况下,生成的委托类和被委托类是在同一个包中。

委托类能够像下面那样被实例化:

//object to be delegated
Object obj = ...;
//some concrete invocation handler instance
DelegationInvocationHandler h = ...;

Constructor c = clazz.getConstructor(new Class[] { Object.class });
Object inst = c.newInstance(new Object[] {obj});
((Delegation) inst)._setInvocationHandler(h);


练习2:创建一个抽象类的委托类

DelegationGenerator也能够生成一个对于抽象类的具体的委托类

//AbstractClass.java
package org.jingle.util.dydelegation.sample;

public abstract class AbstractClass {
public abstract void wave();
}



//AbstractClassTest.java
package org.jingle.util.dydelegation.sample;

import java.lang.reflect.Modifier;

import org.jingle.util.dydelegation.DelegationGenerator;

public class AbstractClassTest {
public static void main(String[] args) {
Class clazz = DelegationGenerator
.getDelegationClass(new Class[] { AbstractClass.class });
System.out.println("Delegation class name = " +
clazz.getName());
System.out.println(
Modifier.isAbstract(clazz.getModifiers()));
}
}


输出:

Delegation class name =
org.jingle.util.dydelegation.sample.AbstractClass_Delegation_0
false

生成的委托类是具体类而不是抽象类。

练习3。创建类和多个接口的委托类

DelegationGenerator.getDelegationClass()能够同时委托一个类和多个接口,生成委托类来委托给定的类和接口。并且,将去除重复的接口。

//Idel1.java
package org.jingle.util.dydelegation.sample.bean;

public interface Idel1 {
public void idel1();
}



//Idel2.java
package org.jingle.util.dydelegation.sample.bean;

public interface Idel2 {
public void idel2();
}



//ComplexClassTest.java
package org.jingle.util.dydelegation.sample;

import org.jingle.util.dydelegation.DelegationGenerator;
import org.jingle.util.dydelegation.sample.bean.Idel1;
import org.jingle.util.dydelegation.sample.bean.Idel2;

public class ComplexClassTest {
public static void main(String[] args) {
Class clazz = DelegationGenerator.getDelegationClass(new Class[] {
ConcreteClass.class, Idel1.class, Idel2.class });
System.out.println(
Idel1.class.isAssignableFrom(clazz));
System.out.println(
Idel2.class.isAssignableFrom(clazz));
System.out.println(
ConcreteClass.class.isAssignableFrom(clazz));
}
}


输出:

true
true
true

生成的委托类扩展了给定的类ConcreteClass,实现了所有的给定接口:Idel1和Idel2。

练习4。创建单个对象的委托对象

根据一个特定的被委托对象,DelegationGenerator能够直接生成一个委托对象。

// ConcreteClassTest2.java
package org.jingle.util.dydelegation.sample;

import java.lang.reflect.Method;

import org.jingle.util.dydelegation.DelegationGenerator;
import org.jingle.util.dydelegation.DelegationInvocationHandler;
import org.jingle.util.dydelegation.DummyInvocationHandler;

public class ConcreteClassTest2 {
public static void main(String[] args) {
ConcreteClass inst = new ConcreteClass();
DelegationInvocationHandler handler =
new SimpleHandler();
ConcreteClass delegation = (ConcreteClass)
DelegationGenerator.newDelegationInstance(inst, handler);
delegation.hello();
delegation.hello2();
System.out.println(delegation.toString());
}
}

class SimpleHandler extends DummyInvocationHandler {
public boolean invokeBefore(Object bean,
Method method, Object[] args)
throws Throwable {
System.out.println("Interrupted by SimpleHandler");
return super.invokeBefore(bean, method, args);
}
}


输出:

Interrupted by SimpleHandler
Hello from ConcreteClass
Hello again from ConcreteClass
Interrupted by SimpleHandler
org.jingle.util.dydelegation.sample.ConcreteClass@ef5502

DummyInvocationHandler是一个DelegationInvocationHandler虚拟的实现。它总是在方法invokeBefore()中返回true,在方法invokeAfter()中直接返回输入的值,并且在invokeAfterException()中直接抛出输入异常throwable。带有DummyInvocationHandler委托对象和被委托对象有相同的动作。

DelegationGenerator.newDelegationInstance()将一个对象和DelegationInvocationHandler实例作为参数。它返回委托对象来委托给定对象。

所有对委托对象的调用方法将被DelegationInvocationHandler实例委托,除了下面的方法:
没有public限定符的方法。
没有final限定符的方法
有static限定符的方法
定义在一个对象类中,除了hashCode(),equals()和toString()而外的方法。

练习5。创建一个Java核心类对象的委托对象

你曾经想过委托存在的Java核心类对象吗?可以像通常所做的那样委托它。

//DateTest.java
package org.jingle.util.dydelegation.sample;

import java.lang.reflect.Method;
import java.util.Date;

import org.jingle.util.dydelegation.DelegationGenerator;
import org.jingle.util.dydelegation.DelegationInvocationHandler;
import org.jingle.util.dydelegation.DummyInvocationHandler;

public class DateTest {
public static void main(String[] args) {
Date date = new Date();
DelegationInvocationHandler handler =
new DateClassHandler();
Date delegation = (Date) DelegationGenerator
.newDelegationInstance(date, handler);
System.out.println("Delegation class = " +
delegation.getClass().getName());
System.out.println("True date = " +
date.getTime());
System.out.println("Delegation date = " +
delegation.getTime());
}
}

class DateClassHandler extends DummyInvocationHandler {
public Object invokeAfter(Object bean,
Method method, Object[] args,
Object result) throws Throwable {
if (method.getName().equals("getTime")) {
return new Long(((Long)result).longValue() - 1000);
}
return super.invokeAfter(bean, method, args, result);
}
}


输出

Delegation class = org.jingle.util.dydelegation.Date_Delegation_0
True date = 1099380377665
Delegation date = 1099380376665

当创建一个Java核心类的委托类时,委托类不会和Java核心类在同一个包中,因为Java安全模型不允许用户定义的ClassLoader定义以java开头的包中的类。

DateClassHandler代理在invokeAfter()中的getTime()方法调用,返回的值比正常返回值低1000。

高级用法

练习6。模拟代理行为

委托能够做代理所做的事吗?绝对可以!动态委托覆盖了代理的功能。给定一个适当的委托句柄,它能够模拟Java代理的行为。

// ProxyTest.java
package org.jingle.util.dydelegation.sample;

import java.lang.reflect.Method;

import org.jingle.util.dydelegation.DelegationGenerator;
import org.jingle.util.dydelegation.DelegationInvocationHandler;
import org.jingle.util.dydelegation.DummyInvocationHandler;
import org.jingle.util.dydelegation.sample.bean.Idel1;
import org.jingle.util.dydelegation.sample.bean.Idel2;

public class ProxyTest {
public static void main(String[] args) {
DelegationInvocationHandler handler = new ProxyHandler();
Object delegation =
DelegationGenerator.newDelegationInstance(null,
new Class[] { Idel1.class, Idel2.class },
null, handler);
((Idel1) delegation).idel1();
((Idel2) delegation).idel2();
}
}

class ProxyHandler extends DummyInvocationHandler {
public boolean invokeBefore(Object bean,
Method method, Object[] args)
throws Throwable {
return false;
}

public Object invokeAfter(Object bean,
Method method, Object[] args,
Object result) throws Throwable {
String name = method.getName();
if (name.equals("idel1"))
System.out.println("Hello from idel1");
else if (name.equals("idel2"))
System.out.println("Hello from idel2");
return super.invokeAfter(bean, method, args, result);
}
}


输出
Hello from idel1
Hello from idel2

ProxyHandler在方法invokeBefore()中返回false,这意味着在委托对象上的所有方法调用都不会代理原来的对象。与代理Proxy相同,它使用方法invokeAfter()来定义委托行为。

DelegationGenerator.newDelegationInstance()方法有另一个版本。它包含4个参数:
被委托的对象
这可能是null。如果它不是null,它必须是所有给定的类和接口的实例

被委托的类数组
这可能包含多个接口,和一个类。

委托类名字
如果是null,系统将会生成一个名字

一个DelegationInvocationHandler实例,使用它来定义委托的行为。
从输出结果,我们可以看到委托对象是两个接口Idel1和Idel2的实例。它的行为就是定义在句柄中的。

练习7.部分委托

直到现在,我们已经委托了指定对象所有的函数。怎么样来进行对象函数的部分委托呢?

//MyDate.java
package org.jingle.util.dydelegation.sample.bean;

import java.util.Date;

public class MyDate extends Date implements Idel1, Idel2 {
public void idel1() {
}

public void idel2() {
}
}



// MyDateTest.java
package org.jingle.util.dydelegation.sample;

import java.util.Date;

import org.jingle.util.dydelegation.DelegationGenerator;
import org.jingle.util.dydelegation.DelegationInvocationHandler;
import org.jingle.util.dydelegation.DummyInvocationHandler;
import org.jingle.util.dydelegation.sample.bean.Idel1;
import org.jingle.util.dydelegation.sample.bean.Idel2;
import org.jingle.util.dydelegation.sample.bean.MyDate;

public class MyDateTest {
public static void main(String[] args) {
MyDate inst = new MyDate();
DelegationInvocationHandler handler =
new DummyInvocationHandler();
Object delegation =
DelegationGenerator.newDelegationInstance(inst,
new Class[] { Idel1.class, Idel2.class },
null, handler);
System.out.println(delegation instanceof Idel1);
System.out.println(delegation instanceof Idel2);
System.out.println(delegation instanceof Date);
}
}


输出

true
true
false

MyDate扩展了Date类,实现了Idel1和Idel2接口。DelegationGenerator.newDelegationInstance()使用MyDate实例作为被委托的实例,将委托范围限制在Idel1和Idel2中。换句话说,生成的委托对象是Idel1和Idel2的实例,而不是Date实例。


结论

Dunamis项目介绍了动态委托模式来扩展Java代理(Proxy)反射机制。它能够在运行时生成类和接口的委托。本文使用简单的例子简短地介绍了动态委托。在实际里,许多领域能够使用动态委托,例如单元测试中的mock对象,Java GUI MVC框架,以及其他方面。

资源
Dunamis 项目: dunamis.dev.java.net
下载本文例子程序:例子代码
BCEL: jakarta.apache.org/bcel/index.html

Lu Jian是有着四年Java开发经验的Java高级架构师、开发者。
Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd
动态委托及其应用

作者:Lu Jian

11/17/2004

翻译:DannyTan


版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处?

标签: