Java 动态代理原理

动态代理

代理模式

代理模式强调在对被代理对象的控制。代理模式知识点不做赘述。

静态代理,代理类的代码是在编译期间就已经确定好的。

动态代理,代理类的代码编译期间是没有的,只有在运行期间才能确定。

简单回顾下静态代理相关代码,以统计方法耗时为例。

//声明接口类
public interface IDoSth {
    void doSth(String s);
}
//声明实际类
public class DoSth implements IDoSth{
    @Override
    public void doSth(String s) {
        System.out.println("doSth:" + s);
    }
}
//声明静态代理类,可对被代理的对象进行定制化处理,此处将 xx 掉包
public class ProxyDoSth implements IDoSth{
    private IDoSth instance;
    public ProxyDoSth(IDoSth ins) {
        this.instance = ins;
    }
    @Override
    public void doSth(String s) {
        long start = System.currentTimeMillis();
        instance.doSth(s);
        System.out.println(System.currentTimeMillis()-start);
    }
}

动态代理相关语法

// 动态代理相关代码
        IDoSth ins = new DoSth();
      IDoSth proxy = (IDoSth) Proxy.newProxyInstance(Test.class.getClassLoader(), ins.getClass().getInterfaces(), new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long start = System.currentTimeMillis();
            Object res = method.invoke(ins, args);
            System.out.println("method: " + method.getName() + " costTime : "
                + (System.currentTimeMillis() - start));
            return res;
        }
      });
        proxy.doSth("做饭");
        // 打印 proxy 对象相关类和接口信息
        System.out.println(proxy.getClass()); 
      System.out.println(proxy.getClass().getSuperclass());
      for (Class cls : proxy.getClass().getInterfaces()) {
            System.out.println(cls);
      }
}
//输出结果
doSth:做饭
method: doSth costTime : 1 // 方法耗时
class com.sun.proxy.$Proxy0 //proxy 类的类名
class java.lang.reflect.Proxy //proxy 类父类
interface jjava.dynamicProxy.IDoSth //proxy 类实现的接口

由 proxy 对象可知,其类名com.sun.proxy.$Proxy0 ,该类父类为 java.lang.reflect.Proxy且实现了 IDoSth 接口。

这里面有两个关键类,一个是 Proxy,一个是InvocationHandler。Proxy 主要负责代理类的生成,InvocationHandler 主要负责用户自定义实现代理类的功能。

动态代理的本质

动态代理没那么玄乎,仅仅是在程序运行期间生成了代理类的代码而已。那这份代码长什么样子呢?

我们将动态生成的 Proxy 类的代码保存在class 文件中,这一步代码如下

//$Proxy0 为生成的代理类的名字,path 为保存的文件名,实际上就是将byte 数组保存到本地,newProxyInstance() 方法内部就是通过这种方式生成的 代理类的 byte[]
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", ins.getClass().getInterfaces());
      String path = "/${projectPath}/jjava/dynamicProxy/$Proxy0.class";
      try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("cls 文件写入成功");
      } catch (Exception e) {
            System.out.println("cls 文件写入失败");
            e.printStackTrace();
      }

生成的 $Proxy0 类的代码如下

public final class $Proxy0 extends Proxy implements IDoSth {
    private static Method m1;   //java.lang.Object#equals()
    private static Method m2;   //java.lang.Object#toString()
    private static Method m3;   //jjava.dynamicProxy.IDoSth#doSth()
    private static Method m0;   //java.lang.Object#hashcode()
    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);
        }
    }
        // 通过 proxy.doSth(xx) 时,本质上调的是这个方法,内部是通过调用 InvocationHandler 中的 invoke 方法实现的。
    public final void doSth(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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"));
            m3 = Class.forName("jjava.dynamicProxy.IDoSth").getMethod("doSth", Class.forName("java.lang.String"));
            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());
        }
    }   
}

通过类信息,可以和签名的log信息互相验证。同时 由于所有动态代理产生的类都是Proxy 的子类,所以我们在使用时,只能用 interfaces 中的任一接口承接,而不能用自定义的其他类型,否则会报 ClassCastException。

可以看到 $Proxy0类的构造函数,包括一个 InvocationHandler 类型的参数,并将该参数传给了父类的函数,该阐述就是我们调用Proxy.newProxyInstance() 方法传入的 h。

我们在调用代理类的方法时,如 proxy.doSth(),本质上是通过super.h.invoke(this, m3, new Object[]{var1}); 实现的。h为我们传入的 InvocationHandler 的实例,m3 为一个Method 对象,最终是通过调用 m3.invoke(target,args ) 实现被代理对象的方法调用。m3 是一个 Method 类的实例。与此同时,代理类 $Proxy0 除了实现所有接口类的方法外,还自动帮我们添加了Object 类 的3个方法,分别是 toString()、hashcode()、equals()。并且在代理类的类初始化阶段对这几个Method 实例进行赋值。

动态代理类的名字规则

类全路径名名 = pkgName + className
包名主要主要取决于接口是否是public 的。如果要代理的接口全都是public 的,则类的全路径名是 com.sun.proxy,否则就是非public 接口的包名。
类名为 {n},n为当前已经创建的动态代理类的个数,由于有缓存的逻辑,只有不同数组接口的代理,才会创建新的类。n从0开始,在同进程内依次递增。源码如下:

    Proxy$ProxyClassFactory#apply()                 
                        /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";// 默认值 com.sun.proxy.
            }

            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num; //proxyClassNamePrefix 为“$Proxy”

个人理解

动态代理是对接口的代理,而不是对类的代理。没有接口则无从代理。

jvm由于不支持运行时修改一个类或修改方法,如修改方法内容,添加方法等。动态代理由于是在运行期间生产了新的代理类去扩展功能,也算是对此种不足提供了一种支持。

动态代理是 AOP 思想的一种实现,完美的实现了一个切面。方便我们在每个方法执行前后定制功能。最常见的就是统计每个方法的执行耗时,在方法执行前记录时间戳,方法执行完后再记录一次。

静态代理也可以做切面编程,只不过当使用静态代理时,手动编写代理接口类的成本太大。相对于静态代理,动态代理是在此层面上做了一层抽象和封装。定制化需求只需要在 InvocationHandler 的 invoke() 方法中添加就可以了。而且 invoke() 方法传入的参数是 一个 object,意味着任何类的实例都可以被代理(只要实现相应接口),大大增强了InvocationHandler 子类复用性。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

很多 android 和 java 框架都应用了动态代理技术。如 Spring,retrofit等。

你可能感兴趣的:(Java 动态代理原理)