天天看点

java Proxy类的讨论前言Proxy模式思想介绍Proxy类介绍Proxy的实现总结参考材料

前言

    我们常用的一些proxy的手法或者设计模式都是本质上通过一个中间的代理类来引用实际的功能实现类。这个代理类提供和实际功能类同样的功能接口,所以从使用者的角度来说看起来是一样的,这样也就看不出区别来。和proxy模式有相似思想的设计模式包括有decorator, adaptor等模式。代理类很多时候并不仅仅只是一个简单的功能转发,有的时候它也可以做一些适当的功能增强。当然,这里很多时候实现的代理是基于一个已知类的声明和功能,然后静态的代码实现相应的规约。所以一个代理类只能代理一个具体的功能类。如果有变化的话,我们需要再静态的去实现适配的类。在这个时候,我们可能会考虑是否有一种可以动态生成的代理类呢?如果我们可以实现动态的生成代理类的话,需要一些什么条件呢?这里我们会讨论proxy模式以及java中Proxy类使用的思路。

Proxy模式思想介绍

    我们一般讨论到Proxy相关的模式时,从字面上就很容易理解,既然是一种代理的模式,肯定是我们本来希望一些使用的功能由某个类来实现,但是由于某些原因我们将一些具体的实现的功能交给另外一个类来做。这个使用那些其他类提供功能的类就称其为代理。在使用的时候,我们用这个代理类或者原来的类是看起来没什么区别的。通常对应的一种关系如下:

java Proxy类的讨论前言Proxy模式思想介绍Proxy类介绍Proxy的实现总结参考材料

    这里的关系也比较好理解,我们既然从用户使用的角度来说,Proxy类和具体实现类看起来没什么区别。那必然从用户的角度他们是可以互换的。所以他们必然需要实现同一个接口才可能。而这种模式典型的示例代码如下:

Contract interface:

public interface Contract {
    public void doSomething();
}
           

 Impl:

public class Impl implements Contract {
    public void doSomething() {
        System.out.println("Do something in Implementation");
    }
}
           

Proxy:

public class Proxy implements Contract {
    private Contract contract;

    public Proxy(Contract contract) {
        this.contract = ocntract;
    }

    public void doSomething() {
        // Can do something overhead
        contract.doSomething();
        // Do something afterward...
    }
}
           

    从使用者的角度来说,如果我们引用Proxy类,它本身将一些功能实现转发给了具体的Impl类,同时它也可以针对实现做一些增强和调整。如果现在我们来看Proxy模式的作用,我们可以发现,对于一个需要被代理的类来说,它的某些需要被代理的功能最好能够提取到某个接口中来以方便建立代理。对于代理的具体实现,我们可以添加自己的可定制部分。另外,这边还有一个问题就是,比如前面的类里有多个方法需要被代理,具体的代理类也需要写很多个对应的代理方法。这些写法不难,只是感觉很重复和琐碎。

和其他模式及应用的关系

    透过前面写的代码和类图关系,我们会发现Proxy模式和一些常用的设计模式有很多相似的地方。一些比较典型的有Decorator, Adaptor。另外,我们从代码里可以看到,在一些代理方法执行的地方,Proxy类可以做一些其他特性的定制,既可以在方法执行前也可以在方法执行后。这些东西和java EE里的AOP概念很接近。

    我们先来看看Decorator模式的类关系描述:

java Proxy类的讨论前言Proxy模式思想介绍Proxy类介绍Proxy的实现总结参考材料

    这里,每个Decorator都有一个指向同样接口的引用。它本身只是做一些代理。继承Decorator的类会针对具体特性做一些增强。这个Decorator的类本身就相当于是一个Proxy。关于Decorator pattern的详细描述可以参考我的这篇文章。从这些关系我们可以看到,Decorator pattern增加的继承可以使得代理增加的特性更加灵活和丰富。

    至于Adaptor pattern的描述,其本身更加简单:

java Proxy类的讨论前言Proxy模式思想介绍Proxy类介绍Proxy的实现总结参考材料

    我们这里的Adapter就是一个Proxy,他本身要引用另外一个Adaptee的特性,只是要使得他本身符合接口Target的规约而已。

    现在,到这一步的时候,我们发现其实原来很多的应用方式都用到了Proxy模式的思想,只是平时不太留意到而已。我们手工编码实现的Proxy需要有明确的接口规约,这样才能模拟出这样的结果来。在一些情况下,如果我们需要动态的生成一些Proxy类的话,有没有什么好的办法呢?因为对于一些接口来说,每次我们通过手工的去实现他们其实从写代码的角度来说挺没劲的,来来去去就是那么个套路。既然这些事情是如此无趣,能不能让程序给我们生成呢?

Proxy类介绍

    在Java里有专门用于动态代理类生成的方法,就是java.lang.reflect.Proxy。它采用反射的机制来调用原来的被封装方法。我们以前面的示例为基础来看看用Proxy类封装代理之后的常用做法。

    前面的示例里我们首先定义了接口Contract和具体的实现Impl类。这里就不重复。为了能够代理这个类的功能,我们首先定义一个类DynamicProxyHandler:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public 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 {
        System.out.println("Proxy: " + proxied);
        System.out.println("Method name: " + method.getName());
        System.out.println("Args: " + args);

        return method.invoke(proxied, args);
    }
}
           

    这个ProxyHandler类实现接口InvocationHandler。我们在实现的invoke方法里添加了一些自己定义的信息。然后通过method.invoke方法来调用目标对象的方法,并返回调用的结果。我们可以说这里是我们定义自己定制部分特性的切入点,在代理类被调用的时候,触发的就是这个方法。同时被代理的对象将作为构造函数的参数传递进来。

    我们再来看是怎么使用这个DynamicProxyHandler将目标对象包装起来:

import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;

public class ProxyDemo {
    public static void main(String[] args) {
        // Original way without proxy
        System.out.println("Run without proxy");
        Contract contract = new Impl();
        contract.doSomething();

        // With proxy
        System.out.println("Run with proxy");
        InvocationHandler handler = new DynamicProxyHandler(contract);
        Contract cont = (Contract)Proxy.newProxyInstance(
            contract.getClass().getClassLoader(), 
            new Class[] { Contract.class }, handler);
        cont.doSomething();
    }
}
           

    这部分代理使用Proxy部分比较有意思的是我们通过Proxy.newProxyInstance可以返回一个被封装的Contract对象。我们需要将Contract的具体对象的,以及它的Class对象和我们定义的handler传入。在调用的时候我们会发现如下的结果:

Do something in implementation!
Proxy: [email protected]
Method name: doSomething
Args: null
Do something in implementation!
           

    我们如果查阅Proxy.newProxyInstance的官方文档,会发现它的方法原型 如下:

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

    这里的classLoader是定义要代理的那个类的classLoader,一般我们通过该类的Class.getClassLoader()来获得。 而这个interfaces则表示我们要代理的类实现的接口,比如这里我们要代理的类Impl实现了接口Contract,如果它实现了多个而且我们需要都代理的话,则需要放在这里。最后的这个InvocationHandler则是方法被代理执行的对象。一般通过实现InvocationHandler来实现。

    现在我们回过头来看看这种Proxy的实现,这里定义的Proxy对象并没有去直接声明实现某个代理类的接口。另外,虽然我们这里传入了需要被代理的对象,如果将传入对象封装的过程封装成方法的话,我们可以传入任何实现该指定接口的对象,而不只是固定的某一个对象。这样我们就有了另外一个好处,那就是只要将实现该接口的对象传进来,我们就可以代理,不需要再手工的编码创建类。我们在开发的时候会简单一些。

和AOP的比较

    如果很多对AOP比较了解的都知道,AOP的实现可以分为静态切入和动态织入两种方式。典型静态切入的库有aspectJ,而动态的主要是CGLib。这几种织入的方式和Proxy比起来更加强大的地方在于他们可以在方法执行的不同阶段切入时并不要求这个类方法是实现某个接口的。而这里Proxy还是受限于接口的定义限制。所以说,可以将Proxy的功能增强当作一个弱化版的AOP。

Proxy的实现

    既然前面我们使用了Proxy的newProxyInstance方法来封装被代理对象,那么在Java里面,它是怎么实现的呢?我们可以找到这个方法的代码:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass(loader, interfaces);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            Constructor cons = cl.getConstructor(constructorParams);
            return cons.newInstance(new Object[] { h });
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        } catch (IllegalAccessException e) {
            throw new InternalError(e.toString());
        } catch (InstantiationException e) {
            throw new InternalError(e.toString());
        } catch (InvocationTargetException e) {
            throw new InternalError(e.toString());
        }
    }
           

    这个方法其实比较简单,就是通过调用类本身的getProxyClass方法来获取到一个我们代理后生成的Class对象,然后再通过反射调用该对象的构造函数来生成目标对象。那么这个getProxyClass是怎么来生成目标Class对象呢?我们再深入的看看。

    getProxyClass方法本身就需要一个能够实现指定一系列接口的类,而且需要这个类是动态生成的。当然,这个类既然是动态生成的,我们就需要针对接口里所有的方法进行审核。如果我们对jvm再深入理解一点的话,会发现一般java里生成的Class文件有一个固定的格式可以遵循的。比如class文件里指明所使用的java的版本,这个类是接口还是类,里面的成员和方法等信息。既然我们需要一个这样的类,我们完全可以通过一个模板的类来生成这样的类文件。以后我们再通过类加载器将其加载进来就可以了。

    getProxyClass方法通过委托ProxyGenerator来生成这个文件,然后再加载进来。而getProxyClass方法主要是检查这些接口的列表是否重复了,这些接口是否都声明在同一个package里面。然后将生成后的Class对象放到一个weak reference的缓存里。这个几百行的方法里其实主要就是做一些检查工作,没什么特殊的地方。

总结

    Proxy模式以及它的一些应用其实平时使用的并不多。更多的时候是在一些J2EE的应用里,比如生成RMI的stub,以前要用工具类rmic来做,现在基本上就是依赖的Proxy。另外,Proxy的思想里也可以用到AOP的应用上。虽然这部分比较少见,但是它和jvm的很多地方关系比较密切,比如class文件的动态生成。以前我们要生成这些需要自己写出来然后用javac编译器来生成,这里自动按照指定的规约来生成,确实比较少见。

参考材料

core java volumn I

head first design patterns

http://www.ibm.com/developerworks/java/library/j-jtp08305/index.html

http://www.infoq.com/cn/articles/cf-java-reflection-dynamic-proxy

http://openjdk.java.net/