动态代理和静态代理

530 阅读3分钟

比如有个需求:在某个方法执行之前添加一条日志。这里我们并不像修改原有的代码。比如原来的类为class1,其中要添加日志的方法为doSomething,我们可以通过代理的方式来进行。其模型如下

也就是说将方法定义为接口,真正的实现类class1实现这个接口,将真正的逻辑写在这里的doSomething中,然后定义一个Proxy类也实现这个接口,但是这个类中有一个class1的实例,在这个类的doSomething方法中添加日志然后执行class1中的doSomething方法。在真正使用的时候我们创建Proxy的实例而不是Class1的实例,从而完成了不修改原有逻辑的情况下,通过代理的方式添加了日志。具体实现代码如下

public interface Class1Interface {
    public void doSomething();
}

public class Class1 implements Class1Interface {
    @Override
    public void doSomething() {
        Log.e("tag", "class1");
    }
}

public class Class1Proxy implements Class1Interface {
    Class1 clzz = new Class1();

    @Override
    public void doSomething() {
        System.out.println("Begin log");
        clzz.doSomething();
        System.out.println("End log");
    }
}

真正使用

Class1Proxy proxy = new Class1Proxy();
proxy.doSomething();

通过这种方式虽然可以达到代理的目的,但是如果还有类也要实现该方法的话,那么就需要很多个对应的Proxy类,这时候就需要动态代理来解决这个问题了。主要用的是Proxy类的newProxyInstance方法,声明如下

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

参数说明

  • loader,设置为目标对象class1对应的ClassLoader
  • interfaces,设置为目标对象class1所实现的接口类型,这里是Class1Interface
  • h,是一个实现了InvocationHandler接口的类对象,我们通过它的构造函数把目标对象class1注入。

代码如下:

Class1 class1 = new Class1();

Class1Interface class1Proxy = (Class1Interface) Proxy.newProxyInstance(
        class1.getClass().getClassLoader(),
        new Class<?>[] { Class1Interface.class },
        new InvocationHandlerForTest(class1));
class1Proxy.doSomething();

通过 Proxy.newProxylnstance 方法创建的对象,是一个实现了 Class1Interface 接口的对 象,也就是 classI Proxy。 执行class1Proxy的doSomething方法,其实是在执行InvocationHandlerForTest类 的 invoke方法。这个 invoke方法是代理模式的设计思想。 它有一个 method参数,执行 method 的 invoke方法,就是在执行 class1 的 doSomething方法。

public class InvocationHandlerForTest implements InvocationHandler {
    private Object target;

    public InvocationHandlerForTest(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        Log.e("tag", "日志开始");
        Object obj = method.invoke(target, objects);
        Log.e("tag", "日志结束");
        return obj;
    }
}

Proxy.newProxyInstance方法可以“套”在任何一个接口类型的对象上,为这个对象增 加新功能,所以我们称之为“动态代理” 。 在插件化的领域, Proxy.newProxyInstance生成的对象,直接替换掉原来的对象,这个技术就是 Hook 技术 。

这里总结一下静态代理与动态代理的区别

  • 静态代理:需要代理对象和目标对象实现一样的接口,静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件。可以在不修改目标对象的前提下扩展目标对象的功能,由于代理对象需要与目标对象实现一样的接口,所以会产生过多的代理类,一旦接口增减方法,目标对象与代理对象都要进行修改。
  • 动态代理:动态代理对象不需要实现接口,但是要求目标对象必须实现接口,动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中