Java 动态代理 Proxy源码详解

代理简介?

我们知道nginx可以实现正向,反向代理。比如我们想请求服务中一个tomcat,一般就是直接访问机器的ip,如果是代理的话,就是先访问中间代理层(nginx),然后nignx跳转到我们的tomcat机器。代理模式也是如此,也有一个Proxy层,通过Proxy层来真正访问我们的类接口。

为什么要有代理?

我们先看nginx实现的代理,他可以事先为我们做很多ip黑名单过滤,负载均衡,权限,甚至我们还可以到代理层改变我们http接口信息。java的Proxy也是如此,可以在访问真正类的时候做一些前置和后置的统一工作。

JDK的动态Proxy实现

比如有个相亲代理机构,java程序员们通过这个代理机构来找老婆,java程序员有个需求,对象必须是 A照杯 的才行,这些就可以统一交给相亲代理机构来做,自己等结果就行了。

代码实现

public class Main {
    // 相亲代理机构
    static class FindWomanProxy implements InvocationHandler{
        // 被代理的对象
        private FindWoman woman;
        public FindWomanProxy(FindWoman woman) {
            this.woman = woman ;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //在真实的对象执行之前我们可以添加自己的操作
            System.out.println("相亲代理正在筛选A照杯的女士");
            // 筛选 完成 交给 程序员去消费相亲
            return method.invoke(woman, args);
        }
        
    }
    // 相亲 接口
    static interface FindWoman {
        public void find();
    }
    // java 程序员相亲
    static class JavaFindWoman implements FindWoman{
        @Override
        public void find() {
            System.err.println("java程序员相亲 ");
            
        }
    }
    public static void main(String[] args) {
        // 构造相亲代理 , 把java程序员传进去 
        FindWomanProxy proxy = new FindWomanProxy(new JavaFindWoman());
        // 机构产出的相亲代理对象,并非传入的 JavaFindWoman 。
        FindWoman findWoman = (FindWoman)Proxy.newProxyInstance(Main.class.getClassLoader(), JavaFindWoman.class.getInterfaces(), proxy);
        findWoman.find();
    }
}

代码解释: java程序员 通过相亲代理机构(FindWomanProxy) 去完成相亲这件事(FindWoman)。代理机构 产出代理对象,然后调用代理对象的相亲方法,会执行筛选A照杯 ,然后java程序员真正进行相亲。

代理的好处

以后PHP程序员相亲,只要在相亲机构中传入PHP程序员就行了。

FindWomanProxy proxy = new FindWomanProxy(new PhpFindWoman());

而且我们要更改 相亲机构 的 筛选 "照杯" 算法也很简单,统一就改了。还有一个很重要的好处:

发现没有,我们JavaFindWoman类是不是 很干净, 没有丝毫的 筛选A照杯 算法 代码。也就是说 可以 实现无侵入式的代码扩展

我很好奇,那个 Proxy.newProxyInstance 方法 是是怎么动态生成代理对象的?生成的代理对象字节码又是什么样子? 于是我继续进行研究。

Proxy.newProxyInstance 实现原理

通过调式jdk源码,发现了内部用了缓存来缓存生成的class,不是每一次都生成,最终生成class的代码在apply里面(缓存部分的我就不讲了)

 private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // 生成的class 前缀
        private static final String proxyClassNamePrefix = "$Proxy";
        // 名字的自增 标识
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                Class<?> interfaceClass = null;
                try {
                   // 加载传入的接口 也就是我们上面的相亲接口  FindWoman.class 
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                if (!interfaceClass.isInterface()) {
                    // 不是接口抛出异常。 所以JDK只能代理接口
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }
            ///////////////这段代码 就是为了产生 proxyPkg  包名
            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
            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) {
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }
            long num = nextUniqueNumber.getAndIncrement();
            // proxyPkg 为 传入接口的 所在包名称
            //proxyClassNamePrefix  :  固定值  $Proxy
            // num : 自增
           // 生成的代理类名称 包名.$Proxy0
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * sun.misc.ProxyGenerator 工具 生成类 的字节流
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
               // 将class字节流 加载到jvm , 从而生成class
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }

        private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);
    }

从上面看出 其实也是借助了 sun.misc.ProxyGenerator 工具 生成class字节流,然后通过native方法 defineClass0 加载到jvm生成class的。 而且上面判断了只能代理接口


上面拿到class 之后 ,然后通过反射,就构造出了代理对象了

 // cl  为上面产生的 class 
final Constructor<?> cons = cl.getConstructor(constructorParams);
 // 反射 构造实例
return cons.newInstance(new Object[]{h});


现在我觉得我最关注的就是这个class 里面内容到底是什么? jdk提供一个参数让我们把class的文件download出来

    public static void main(String[] args) throws IOException {
    	 //生成$Proxy0的class文件  
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");  
        // 构造相亲代理 , 把java程序员传进去 
        FindWomanProxy proxy = new FindWomanProxy(new JavaFindWoman());
        FindWoman findWoman = (FindWoman)Proxy.newProxyInstance(Main.class.getClassLoader(), JavaFindWoman.class.getInterfaces(), proxy);
        findWoman.find();
    }

sun.misc.ProxyGenerator.saveGeneratedFiles 设置为true后,会生成class文件

https://pic1.zhimg.com/v2-276685c24c82aa116bd891a68949ed48_b.jpg

我们通过反编译工具 jd-gui 就可以看到 class内容

import debug_jdk8.;
import debug_jdk8.Main;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class Proxy0 extends Proxy implements Main.FindWoman {
  private static Method m1;
  
  private static Method m3;
  
  private static Method m2;
  
  private static Method m0;
  
  public Proxy0(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject) {
    try {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final void find() {
    try {
      this.h.invoke(this, m3, null);
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final String toString() {
    try {
      return (String)this.h.invoke(this, m2, null);
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  public final int hashCode() {
    try {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
  
  static {
    try {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("debug_jdk8.Main$FindWoman").getMethod("find", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw new NoClassDefFoundError(classNotFoundException.getMessage());
    } 
  }
}

内部确实很用心, 把toString,hashCode,equals方法都代理了。也继承了 Proxy类,实现了我们的接口。Proxy类:

public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;
    protected Proxy(InvocationHandler h) {
        this.h = h;
    }
    ...
}

我们就关注下我们代理 的 find 方法吧

  public final void find() {
    try {
     // h : InvocationHandler 也就是我们的相亲代理机构 FindWomanProxy 
     // m3 
      this.h.invoke(this, m3, null);
      return;
    } catch (Error|RuntimeException error) {
      throw null;
    } catch (Throwable throwable) {
      throw new UndeclaredThrowableException(throwable);
    } 
  }
 static {
    try {
       // 代理的接口的 find 方法 Method对象
      m3 = Class.forName("debug_jdk8.Main$FindWoman").getMethod("find", new Class[0]);
      return;
    } catch (NoSuchMethodException noSuchMethodException) {
      throw new NoSuchMethodError(noSuchMethodException.getMessage());
    } catch (ClassNotFoundException classNotFoundException) {
      throw new NoClassDefFoundError(classNotFoundException.getMessage());
    } 

也就是回调了我们的代理机构的 InvocationHandler 方法了

这样当调用代理对象的find 方法时 , 也就回调到上面这个红色的方法了。到此,面纱已经解开。我们在来自己实现一个简易的JDK动态Proxy。


自己实现JDK 动态Proxy

我们不用 sun.misc.ProxyGenerator 来生class了,我们用 Javassist 技术动态生成class。

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。它可以用来检查、”动态”修改以及创建 Java类。其功能与jdk自带的反射功能类似,但比反射功能更强大。

算了,下面还有很多内容要讲,我另起炉灶吧,要想自己实现一个JDK的动态代理,请您移步到这。

罗政:自己实现Java 动态代理 Proxy


强烈推荐一个 进阶 JAVA架构师 的博客

Java架构师修炼

本文完~

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者