动态委托及其应用

来源:互联网 发布:中国网络电视台手机版 编辑:程序博客网 时间:2024/05/01 03:34

动态委托及其应用

Lu Jian 2004-11-17

 

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

 

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

 

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

代理预览

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

 

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


1. IdelProxy的类图

下面是相关的简要代码。

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

委托与代理

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

 

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


2. TestBeanDelegation的类图

 

 

TestBeanDelegation实现了Delegation接口并且继承了TestBean类。它也包含了对TestBeanDelegationInvocationHandler的引用。所有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)。它能够分析存在的类,并且,对于接口,抽象类,甚至运行时的具体类来说,它能够生成以字节编码委托类。

 

被委托的接口,类应该满足如下条件:

 

l         动态委托最多只能委托一个类,但是能够代理多个接口。

这个限制来自于Java的单继承模式。一个Java类最多只有一个父类。既然生成的委托类把被委托类作为它的父类,那么指定多个被委托类是不合理的。如果没有指定被委托类,那么缺省的父类就是Object

 

l         被委托类应该有一个有限定符public或者protected的缺省构造函数。

委托类会在它自己的构造函数中调用父类的缺省构造函数。

 

l         被委托类不能是final,而应该对于它的调用者可见。

Proxy生成的代理类是final的。而动态委托却不能接受这种情况。

 

l         动态委托不能委托实现了接口Delegation的任何类。

既然类已经是一个委托类,就没有必要重新委托它。

 

生成的委托类有下面的特点:

l         委托类是在运行时产生的,没有类文件

l         委托类实现了所有继承的接口,扩展了被委托的类。

l         委托类也实现了Delegation接口。

l         委托类有一个接受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()));
    }
}

输出:

 

The generated delegation class is a concrete class instead of an abstract class.

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

 

练习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,实现了所有的给定接口:Idel1Idel2

t

练习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实例,使用它来定义委托的行为。

从输出结果,我们可以看到委托对象是两个接口Idel1Idel2的实例。它的行为就是定义在句柄中的。

练习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类,实现了Idel1Idel2接口。DelegationGenerator.newDelegationInstance()使用MyDate实例作为被委托的实例,将委托范围限制在Idel1Idel2中。换句话说,生成的委托对象是Idel1Idel2的实例,而不是Date实例。

结论

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

Reference

  • Dunamis project: dunamis.dev.java.net
  • Download sample code for this article: sample code
  • 下载本文例子程序:例子代码
  • BCEL: jakarta.apache.org/bcel/index.html

Lu Jian is a senior Java architect/developer with four years of Java development experience.

Lu Jian是有着四年Java开发经验的Java高级架构师、开发者。

原创粉丝点击