为什么需要动态代理?

我们一般使用代理模式来增强函数的功能,比如在函数执行前记录log信息,进行安全性检测等。

常见的代理有静态代理和动态代理。在静态代理中,一个被代理的对象需要一个相应的代理类,当程序中所需代理的对象数量较多时,代理类的数量也会随之增多,这就导致了程序的体积膨胀,在运行时会占用更多的内存。除此之外,被代理的对象添加新的函数时,代理类也要随之添加对应的函数,这就导致了后期维护的不方便。

所谓动态代理,即通过字节码生成的方式动态的组装一个代理类(.class),无需在源码中单独编写代理类文件(.java)。与静态代理相比,动态代理中的代理类是在运行时产生的,而静态代理是在编译时产生的。

Java内置的动态代理

首先提供所需代理的接口与其对应的实现类:

Hello.java

public interface Hello {

    void sayHola();

    void sayHello();
}

HelloImpl.java

public class HelloImpl implements Hello {
    @Override
    public void sayHola() {
        System.out.println("Hola");
    }

    @Override
    public void sayHello() {
        System.out.println("Hello");
    }
}

之后实现内置的InvocationHandler接口:

MyInvocationHandler.java

public class MyInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("sayHola")) {
            System.out.println("代理sayHola()");
        } else if (method.getName().equals("sayHello")) {
            System.out.printf("代理sayHello()");
        }
        System.out.println("方法调用前");
        Object o = method.invoke(target, args);
        System.out.println("方法调用后");
        return o;
    }
}

最后通过内置的Proxy单例类来生成代理类:

Main.java

public class Main {
    public static void main(String[] args) {
        Hello target = new HelloImpl();
        InvocationHandler handler = new MyInvocationHandler(target);
        Hello proxy = (Hello) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
        proxy.sayHello();
        proxy.sayHola();
    }
}

输出:

方法调用前
Hello
方法调用后
方法调用前
Hola
方法调用后

通过程序运行结果可以看出,在生成的代理对象proxy调用sayHello()sayHola()时,分别触发了我们所编写的对应逻辑,即实现了对Hello接口的代理。

在生成动态代理字节码时,默认不保存动态生成的字节码文件,想要观察生成的字节码文件需要设置如下参数:

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

此时可以在项目根目录下的com.sun.proxy路径中找到生成的动态代理类:

$Proxy0.class

public final class $Proxy0 extends Proxy implements Hello {
    private static Method m1;
    private static Method m4;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sayHello() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void sayHola() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("DynamicProxyTest.Hello").getMethod("sayHello");
            m3 = Class.forName("DynamicProxyTest.Hello").getMethod("sayHola");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到生成的动态代理类继承了Proxy类并通过反射的方式在静态代码块中获取了相关接口中的方法。该类内部的函数调用均通过传入的InvocationHandler对象来间接触发。

为什么Java内置的动态代理只能代理接口实现类?

Java内置的动态代理存在限制,即使用动态代理的对象必须实现一个或多个接口,如果想要突破上述限制,只能通过第三方的库,比如cglib来实现。

为什么要这样设计?个人认为,主要原因是代理接口比代理具体类更加灵活,所以jdk的开发者设计了这样的动态代理实现方式,即通过自定义的InvocationHandler和相应的代理类信息(ClassLoader和实现的Interface等)作为模板来生成相应的Proxy对象,此时生成的对象已经继承了Proxy类,考虑到Java单继承机制的限制,也自然无法进一步代理继承类。除此之外,如果要判断某个对象是否是通过动态代理获得的,可以通过该对象是否继承Proxy类来判断(当然也可以通过在生成的代理对象中额外实现一个标记性质的空接口来达到相同的效果)。