2016-08-06 19:00:48 +0000   |     java dynamic proxies   |   Viewed times   |    

先贴一片讲动态代理的好文,《Java 动态代理机制分析及扩展》

什么是代理?

先从代理开始讲。 代理这种设计模式其实很好理解,基本就是最简单的一个“组合”。比如说下面这个例子,我们有A这个类,本来可以直接调用A类的foo()方法。但代理模式就非要把A类当成B类的一个成员字段放在B类里面。然后因为A类和B类都实现了Interface这个接口,所以B类里也有foo()方法。而且B类里的foo()方法就是傻瓜式的调用A类的foo()方法。

interface Interface{public void foo();}

class A implements Interface{
    public void foo(){System.out.println("Method a of class A!");}
}

class B implements Interface{
	public A a=new A();
    public void foo(){a.foo();}
}

class Consumer{
    public static void consum(Interface i){
        i.foo();
    }
}

public class TestProxy{
    public static void main(String[] args){
        Interface i=new B();
        Consumer.consum(i);
    }
}

代理有什么好处?

乍一看,代理方法完全是多此一举,B类的行为和A类完全一样,没有任何意义。但实际上,B类的foo()方法在直接调用A类foo()方法之前和之后,可以做很多事情。举个例子,如果在B类里加个静态计数字段count,然后每次调用foo()方法之后都计一下数,就可以监控A类foo()方法被调用的次数。

class B implements Interface{
	public static long count=0;
	public A a=new A();
    public void foo(){a.foo();count++;}
}

所以代理类里能非常好地控制,辅助被代理类,甚至是增加额外的功能。而且一般来说代理类B和被代理A都会实现同样的接口,这样对用户端(就是上面例子里的Consumer类)的代码没有任何影响,耦合很低。

什么是动态代理?

上面例子里在A类外面套一个B类好像很简单,但实际到了工程级别的代码,需要代理的就不止一个两个了。每个代理类都手动写会累死,而且很枯燥,是没有技术含量的重复。所以这个时候Java的反射功能就立功了。每个对象所对应的Class类对象,不是可以用来动态加载类,动态获取类的成员字段,方法,以及动态调用方法吗?那能不能利用反射动态地生成这个代理类B?

class B implements Interface{
	public A a=new A();
    public void foo(){a.foo();}
}

所以java.lang.reflect.Proxy的作用就是用来动态根据被代理类A来生成代理类B的。

怎么手动“实现”动态代理?

下面这个例子,简单演示了一下在B类已经写好的情况下,怎么通过反射动态加载B类,然后调用B类的foo()方法。

public class TestDynamicProxy{
    public static void main(String[] args){
        try{
            Class<?> refB=B.class;
            Method refFoo=refB.getDeclaredMethod("foo");
            Object refObj=refB.newInstance();
            refFoo.invoke(refObj);
        }catch(Exception e){
            System.out.println(e);
        }
    }
}

B.class获得了B类的Class对象。 Class#getDeclaredMethod()方法根据方法的名称”foo”获得了foo()方法的Method对象。 然后,调用这个Method对象的invoke()来执行这个方法。

但上面这个方法只是在B类已经写好了的情况下有效。其实并没有动态生成B类,根本不能叫动态生成。真的要完全凭空用反射“写”一个B类的字节码文件出来,其实要复杂地多,这就是为什么需要Proxy工具来替我们做的原因。

Proxy类怎么实现动态代理?

Proxy类里能替我们生成一个代理类对象的,就是newProxyInstance()方法。现在回过头看它的三个参数,

newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

第一个ClassLoader是类加载器。第二个参数是一组需要实现的接口。包含了代理类需要实现的接口方法的信息。最后一个最重要的就是一个实现了InvocationHandler接口的对象。InvocationHandler接口在java.lang.reflect包里。最主要的就是定义了invoke(Object proxy, Method method, Object[] args)方法。看它的三个参数就知道,它就是假设在已经动态生成了最后的proxy代理对象,以及所有接口定义的方法Method对象以及方法的参数的情况下,定义我们要怎么调用这些方法的地方。所以最主要的业务逻辑应该是在第三个参数InvocationHandler的invoke方法里定义。可以在调用被代理类方法的时候添加一些增强行为。

下面代码,是根据之前A类B类的例子用Proxy类实现动态代理的Demo。代码里原先的B类被擦掉了,完全由Proxy类动态生成。

interface Interface{public void foo();}

class A implements Interface{
    public void foo(){System.out.println("Method a of class A!");}
}

/*	//这是Proxy要动态生成的B类。
class B implements Interface{
    public void foo(){a.foo();}
    public A a=new A();
}
 */

class Consumer{
    public static void consum(Interface i){
        i.foo();
    }
}

class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;
    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try{
            return method.invoke(proxied, args);
        }catch(Exception e){
            System.out.println(e);
            return null;
        }
    }
}

public class TestDynamicProxy{
    public static void main(String[] args){
        A a=new A();
		//直接把A类对象a当参数传进去,就动态产生一个代理类对象
        Interface proxy = (Interface)Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class<?>[]{Interface.class }, new DynamicProxyHandler(a));
        Consumer.consum(proxy);
    }
}

在实现了InvocationHandler接口的DynamicProxyHandler类里有一个被代理类的对象proxied作为成员字段。在获得了参数传进来的代理类对象和Method对象之后,直接用Method#invoke(Object o)方法,调用了代理类对象的方法。而且就像之前说的,在调用方法的前后可以添加一些前后增强操作,比如说加一个缓冲区,或者某些统计工作。所以InvocationHandler类实际上是一个简单的调用被代理类方法的”小框架”。

然后,剩下的事就交给Proxy来完成。其实newProxyInstance()方法隐藏了底层很大的复杂性。关键的难点在于怎么根据给定的ClassLoader和接口的方法信息动态生成一个所谓“空壳”。生成空壳的过程中要用到自动生成的字节码。事情每次进展到字节码的范畴总是变得很困难,普通程序员很难搞清楚里面到底发生了什么,只能说,好吧,虚拟机为我们自动生成了一个代理类。

这里截取newProxyInstance()方法源码里比较重要的一段贴上来,

				/*
                 * Choose a name for the proxy class to generate.
                 */
                long num;
                synchronized (nextUniqueNumberLock) {
                    num = nextUniqueNumber++;
                }
                String proxyName = proxyPkg + proxyClassNamePrefix + num;


                /*
                 * Generate the specified proxy class.
                 */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces);
                try {
                    proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    throw new IllegalArgumentException(e.toString());
                }

可以看到,proxyName是动态生成的代理类的名称,一般是·$ProxyN的格式。N代表代理是N次生成动态代理。

然后见证奇迹的时刻到了,关键的核心步骤有两个: ProxyGenerator.generateProxyClass()方法生成了类加载器要用到的字节码。它需要的参数只有两个,1)类名,2)实现的接口的Class对象。然后它就神奇地生成了一堆字节码byte[],基本就是一个凭空造出来的编译好的.class文件。这个方法来自神秘的sun.misc包。也查不到源码。 最后神秘的字节码和加载器,以及类名一起被交到另一个native方法defineClass0( )里,由它生成代理类的Class对象。至于native方法怎么实现,源码里也查不到。

最后再总结一下,使用Proxy的三步, 在第三个参数,实现InvocationHandler接口的对象的invoke()方法里写业务逻辑。 第二个参数是代理实现接口的Class对象 第一个参数是一个ClassLoader。一般直接用调用类的加载器。