动态代理

深入浅出动态代理

Posted by gambol on March 5, 2016

动态代理


代理是什么?

在现实生活中,存在这样一种人—- 代理人。

举一个例子,你目前在加州,看见现在北京的房产价格很高,想卖掉手头的房。怎么办呢?只能委托给你在国内的父母进行售卖。你作为业主,需要做的事情是:签合同。 因为你不能回国,所以,你父母就替你卖房。 当然,父母比你要操心更多。譬如带买家看房,选择一个靠谱的买家,还要把钱打给你。

这个时候,父母就是你的代理。父母替你完成了许多你暂时不能(想)完成的工作

在计算机世界里,也经常需要这种角色。为什么在代码里也需要这种角色呢?有几个优点:

  1. 职责清晰(核心角色只管签合同)
  2. 扩展性好

我们先写一个代码看

public interface HouseSeller {

    boolean sell(int money, int peopleId);

    void signContract(int peopleId);

}

class $Young implements HouseSeller {

    @Override
    public boolean sell(int money, int peopleId) {

        if (money > 100) {
            System.out.println("ok, deal");
            return true;
        } else {
            System.out.println("no, i want more");
            return false;
        }
    }

    @Override
    public void signContract(int peopleId) {
        System.out.println("done. bye bye");
    }
}

class $Old implements HouseSeller {

    $Young young;

    public $Old($Young young) {
        this.young = young;
    }

    @Override
    public boolean sell(int money, int peopleId) {
        // 代理人替实体干活
        if (peopleId < 0) {
            System.out.printf("no, not a good man");
            return false;
        }

        return young.sell(money, peopleId);
    }

    @Override
    public void signContract(int peopleId) {
        // 找律师, 找房产证

        young.signContract(peopleId);
    }
}

在这个代理里, Old就是Young的代理。代理通常做的事情如下图 代理所做的事情


动态代理又是什么?

从上面的代码可以看出来,代理这种现象是存在的,也能解决不少问题。 但是明眼人都能看出来,上面的生成的那种代理(通常叫做静态代理)代码很罗嗦。

懒惰的程序员又整出了一个新的模式,动态代理 ,用来解决啰嗦代码。解决方案是什么呢? 第一招: 找了一个InvocationHandler来handle所有调用原始代码的地方。 InvocationHandler

如上图,如果想要InvocationHandler(或者说,代理)发挥作用,必须要Proxy和RealSubject能完成同样的功能。 怎么才能做到这个约定呢? 有两种方案

  1. 接口。 让Proxy 和RealSubject 实现同一个接口。(这就是JDK提供的动态代理机制)
  2. 继承。 让Proxy继承RealSubject。 这样Proxy就有了RealSubject的功能,还能改写RealSubject的功能呢。(这是cglib提供的代理机制)

JDK默认动态代理(InvocationHandler)

使用这种机制之前,切记JDK的默认代理机制是基于接口的,也就是说,需要被代理的类(RealSubject)必须要实现某个接口, 只有接口里的方法才能被代理生成。

接着上面的代码,我们也用JDK默认的动态代理来写一个代码看看


class Handler implements InvocationHandler {

    $Young young;

    public HouseSeller bind($Young young) {
        this.young = young;
        // 重点关注
        return (HouseSeller)Proxy.newProxyInstance(young.getClass().getClassLoader(), young.getClass().getInterfaces(), this);
    }

    // 重点关注
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {

        System.out.println("I am proxy: kakaka");

        return method.invoke(young, args);
    }


    // 真正调用的地方
    public static void main(String[] args) {
        Handler old = new Handler();

        HouseSeller seller = old.bind(new $Young());

        int peopleId = 3;
        int money = 200;
        if (seller.sell(money, peopleId)) {
            seller.signContract(peopleId);
        }
    }
}

重点注意几个地方:

  • 我们要新写一个代码, 实现InvocationHandler接口. InvocationHandler里,只有一个方法
/**
 * proxy:  指代我们所代理的那个真实对象
 * method:  指代的是我们所要调用真实对象的某个方法的Method对象
 * args:  指代的是调用真实对象某个方法时接受的参数
**/
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
  • Proxy.newProxyInstance 这个代码生成了真正的动态代理代码. 具体的实现代码见下
/**
 * loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
 *
 * interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
 *
 * h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
**/

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ................
        final Class<?>[] intfs = interfaces.clone();
        
        /*
         * 生成/找到 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<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } 
    }

从上面你的代码可以看出来, 使用InvocationHandler使用一个invoke函数代替了之前的 sell, signContract函数. 恩,确实让大家懒惰了一些. (当然,JVM最后看到的代码, 仍然有sell, signContract.)


CGLIB

JDK的默认方法不错,但是有一个约束条件: 这个被代理的类,必须实现某个接口. 但是现实情况是,不是所有的想要被代理的类都实现了某个接口. 因此,这个时候CGLIB(Code Generation Library)诞生了.

cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

还是接着写买房的代码, 我们看看使用CGLIB的情况下, 怎么写

class Hacker implements MethodInterceptor {

    //要代理的原始对象
    private Object obj;

    public Object createProxy(Object target) {
        this.obj = target;
        // cglib里的增强类
        Enhancer enhancer = new Enhancer();

        // 设置代理目标
        enhancer.setSuperclass(this.obj.getClass());

        // 设置回调. 这是相当于代理类上的所有方法, 都会使用这个callback. callback使用intercept进行拦截
        enhancer.setCallback(this);

        return enhancer.create();
    }


    public Object intercept(Object paramObject, Method paramMethod, Object[] paramArrayOfObject, MethodProxy paramMethodProxy)
            throws Throwable {
        System.out.println("I am hacker go");
        Object o = paramMethodProxy.invokeSuper(paramObject, paramArrayOfObject);
        return o;
    }

    public static void main(String[] args) {
        $Young proxyYoung =  ($Young)new Hacker().createProxy(new $Young());

        int peopleId = 3;
        int money = 200;
        if (proxyYoung.sell(money, peopleId)) {
            proxyYoung.signContract(peopleId);
        }
    }
}

从上文的代码可以看到CGLIB的核心是 Enhancer类,名字就霸气,叫做增强类. 在Spring的AOP很多生成的都是这种增强类.


动态代理的机制是什么?

上文说了很多动态代理的写法. 我们回头来想一想, 为什么JVM在能支持动态代理, InvocationHandler 和 cglib是怎么做到动态代理代码的生成的呢?

通常情况下, JAVA编译器将.java文件编译成.class文件,.class文件里放的是JVM才能识别的机器码. JVM在运行时, 解释.class文件, 加载到内存里, 生成Class对象.

这么说, 如果想要做动态代理, 其实只需要在运行时,生成对应的二进制文件(.class文件),传给JVM, JVM将这个文件解释,就动态的生成了一个Class对象.

参考下图: 43918004_2-93.3kB

可以利用工具 ASM 或者 javaassist来做生成 class的事情

动态代理有什么用

OK, 最后,我们来说一下, 代理有什么用.

通常情况下, 我们都不裸着用代理. 常用代理的地方包括:

  1. Spring的bean. 在Spring 上下文里, 所有的bean 都是代理之后的bean. 方便做一些功能的增强.
  2. AOP.

最后,说一个具体的例子. 需求是: 写一个方法, 执行这个方法时, 会调用所有有@A注解的类里的testXXX()方法. 解法 :

  1. 可以直接使用AspectJ来实现
  2. 也可以自己手写代码实现.

参考文献

  1. http://www.360doc.com/content/14/0801/14/1073512_398598312.shtml