浅谈Java动态代理

代理模式

相信了解设计模式的developer对代理(proxy pattern)模式都不陌生。代理模式的基本思想就是在调用者和被调用者之间加上一层“代理”,这层代理对于调用者而言是透明的,因为代理往往和被代理对象实现相同的借口。那么既然实现相同的接口,代理的意义又何在?因为我们常常需要在原本的接口上封装一些业务逻辑,比如日志、缓存、访问控制等等,这些另外封装的业务逻辑从设计的角度并不适宜直接封装在原有的接口实现中,因为诸如日志、缓存等并不属于被代理对象的职责;同时,代理模式可以做到方便的修改和移除(设计模式的关键就是封装变化)。这种模式在RMI和EJB中都得到了很好的体现,包括spring的AOP中实现。传统的代理模式需要在源代码中添加一些附加的类,一般需要手工编写或工具自动生成。

动态代理

java的动态代理比代理的思想更进了一步,因为它可以动态的创建代理并动态的处理对所代理方法的调用,在运行时刻,可以动态创建出一个实现了多个接口的代理类。每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler 接 口的实现。当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandler的invoke方法。在 invoke方法的参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数。invoke方法的返回值被返回给使用者。这种做法实际上相 当于对方法调用进行了拦截。下面以一个简单的示例来说明java动态代理:
接口定义:

interface DoSomething {
    void doSomething();

    void doSomethingElse(String arg);
}

实现接口的类,也即被代理的类:

class RealObject implements DoSomething {

    @Override
    public void doSomething() {
        System.out.println("realObject doSomething");
    }

    @Override
    public void doSomethingElse(String arg) {
        System.out.println("realObject doSomethingElse " + arg);
    }

}

动态代理的处理类,需要实现InvocationHandler接口:

class DynamicProxyHander implements InvocationHandler {
    // 被代理类,将其作为实例变量在构造函数中传入
    private Object proxied;

    public DynamicProxyHander(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("***** proxy: " + proxy.getClass().getSimpleName() + "method :" + method + "args: " + args);
        if (null != args) {
            for (Object arg : args) {
                System.out.println(" " + arg);
            }
        }
        return method.invoke(proxied, args);
    }
}

调用RealObject和调用动态代理生成对象:

public class DynamicProxyDemo{
    public static void consume(DoSomething doSomething){
        doSomething.doSomething();
        doSomething.doSomethingElse("bonbo");
    }

    public static void main(String[] args) {
        RealObject real = new RealObject();
        consume(real);
        DoSomething proxy = (DoSomething)Proxy.newProxyInstance(DoSomething.class.getClassLoader(),
            new Class[]{DoSomething.class}, new DynamicProxyHander(real));
        consume(proxy);
    }
}

通过调用Proxy.newProxyInstance可以创建动态代理,这个方法需要一个类加载器,一个希望该代理实现的接口列表,以及InvocationHandler的一个实现。动态代理可以将所有的调用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个实际对象的引用,从而使动态代理处理中介任务时,可以将请求转发。

动态代理与字节码生成技术

上面说的是动态代理的基本用法,相信许多java开发者都使用过动态代理,即使没有直接 使用过java.lang.reflect.Proxy或实现过InvocationHandler接口,也应该使用过spring做过Bean的管理。如果使用过Spring,那大多数情况下都会用过动态代理,因为如果bean是面向接口编程,那么在spring内部都是用动态代理对bean进行增强的。动态代理中所谓的动态,是针对代码实际编写了代理的“静态”而言的,动态代理的优势不在于减少了那一点的编码量,而是实现了在原始类和接口未知的时候,就确定代理的代理行为,当代理类和原始类脱离直接联系后,就可以和灵活的重用到不同的应用场景中。
在上述的代码里,唯一的黑匣子就是Proxy.newProxyInstance方法,除此之外并无任何特别之处。这个方法返回了一个实现了DoSomething的接口。跟踪这个方法的源码:

public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

我们可以看到程序进行了验证、优化、缓存、同步、生成字节码、显式类加载等操作,最后调用了sun.misc.ProxyGenerator.generateProxyClass方法来完成生成字节码的动作(上述源代码只贴出了主方法,详细步骤有兴趣的读者可以参阅java.lang.reflect.Proxy源代码)。这个方法可以在运行时产生一个描述代理类的字节码byte[]数组。大致的生成过程其实就是根据.class文件的格式规范去拼装字节码,但在实际开发中,直接生成字节码的例子极为少见,如果有大量操作字节码的需求,还是使用封装好的字节码类库比较合适。(关于字节码格式以及类加载过程,读者可以自行查阅资料学习)

动态代理的运用

动态代理由于本身灵活的特性,在Java技术栈中得到了非常多的运用。比如为java开发者所熟悉的spring框架,其AOP本质上也是动态代理。包括hadoop RPC在内的许多RPC框架也大量运用了动态代理,在日后的学习和实践中,会多多关注所运用的工具和框架的实现机制,若有所感悟和收获,会记录一下以供总结和分享。

你可能感兴趣的:(java,设计模式)