MENU

谈一谈 JDK 的动态代理

September 27, 2020 • Read: 3219 • 后端

设计模式---proxy

什么是代理

增强一个对象的功能

买火车票,app就是一个代理,他代理了火车站,小区当中的代售窗口

java当中如何实现代理

java实现的代理的两种办法

1、代理的名词

代理对象 增强后的对象

目标对象 被增强的对象

他们不是绝对的,会根据情况发生变化

2、静态代理

继承

代理对象继承目标对象,重写需要增强的方法;

缺点:会代理类过多,非常复杂

聚合

目标对象和代理对象实现同一个接口,代理对象当中要包含目标对象。

缺点:也会产生类爆炸,只不过比继承少一点点

总结:如果在不确定的情况下,尽量不要去使用静态代理。因为一旦你写代码,就会产生类,一旦产生类就爆炸。

3、动态代理

几种方式:CGLIB、JDK动态代理、Javassist(hibernate的代理实现方法)

动态产生一个类:增强的内容+目标对象

3.1 自己模拟的动态代理

不需要手动创建类文件(因为一旦手动创建类文件,就会产生类爆炸),通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件生成class文件,继而利用UrlclassLoader(因为这个动态产生的class不在工程当中所以需要使用UrlclassLoader)把这个动态编译的类加载到jvm当中,最后通过反射把这个类实例化。

缺点:首先要生成文件

缺点:动态编译文件 class

缺点:需要一个URLclassloader

这些缺点导致:软件性能的最终体现在IO操作

3.2 JDK动态代理

通过接口反射得到字节码,然后把字节码转成Class(使用native 方法:defineClass0())。

JDK动态代理是继承了Proxy然后实现需要代理的接口,由于Java是单继承,所以JDK动态代理只能基于接口

JDK动态代理,代理类继承Proxy类,实现了传入的接口类,代理的方法的内容是: 直接调用你实现的 InvocationHandler 的invoke方法里面的逻辑。

一个Demo

  1. Service接口

    public interface Service {
        void query();
        List<Integer> lists();
    
    }
  2. ServiceImpl实现类和 InvocationHandler实现类

    public class ServiceImpl implements Service{
        public void query() {
            System.out.println("impl");
        }
    
        public List<Integer> lists() {
            List<Integer> list = new ArrayList<Integer>();
            list.add(1);
            return list;
        }
    }

    InvocationHandler实现类

    public class MyInvo implements InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("invoke");
            return null;
        }
    }
  3. Test类

    public class Test {
        public static void main(String[] args) throws Exception {
            Service o = (Service) Proxy.newProxyInstance(Test.class.getClassLoader(),
                    ServiceImpl.class.getInterfaces(), new MyInvo());
            o.query();
            // Proxy.newProxyInstance()最终会调用 ProxyGenerator.generateProxyClass()方法生成字节码文件,
            // 然后调用 Proxy.defineClass0()方法,根据字节码生成代理类的类对象(也就是Class对象),这是个native方法
            byte[] ts = ProxyGenerator.generateProxyClass("Ts", ServiceImpl.class.getInterfaces());
            // 所以我们可以通过把这个字节码写到一个文件来查看生成的代理文件
            File f = new File("./test1.class");
            FileOutputStream out = new FileOutputStream(f);
            out.write(ts);
        }
    }
  4. 展示生成的Ts类部分内容

这个生成的Ts类里面

// 生成的test1.class,是放在idea里面进行反编译后查看的
public final class Ts extends Proxy implements Service {
    private static Method m1;
    private static Method m3;
    // ......

    public Ts(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            // 这个h就是代表InvocationHandler实现类,所以用代理类调用方法时,调用的是invoke()方法里面的实现逻辑
            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 query() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
//......
}

源码解析

下面的源码解析只是简单的说下流程,还有很多东西没说(但是好像都不太重要,跟产生代理类没有关系,更多的是为了性能。比如:缓存这些)

  1. 首先找到入口,在Test类创建代理类,调用的是Proxy.newProxyInstance()方法

    public class Test {
        public static void main(String[] args) throws Exception {
            Service o = (Service) Proxy.newProxyInstance(Test.class.getClassLoader(),
                    ServiceImpl.class.getInterfaces(), new MyInvo());
            o.query();
        }
    }
  1. 查看Proxy.newProxyInstance()方法

    • 在Proxy这个类当中首先实例化一个对象ProxyClassFactory

image-20200805224205592.png

  • 然后在这个方法内部调用getProxyClass0()方法

    public static Object newProxyInstance(ClassLoader loader,
                                        Class<?>[] interfaces,
                                        InvocationHandler h)
      throws IllegalArgumentException
      {
     //......
      
      // 这个就是代理类的Class对象,这里是关键地方,需要查看里面的Class是如何生成的
      Class<?> cl = getProxyClass0(loader, intfs);
       
      try {
       
          // constructorParams = {InvocationHandler.class} ,
          // 得到代理类的里参数对象类型为InvocationHandler的构造器对象
          final Constructor<?> cons = cl.getConstructor(constructorParams);
          final InvocationHandler ih = h;
          //......
          // 用构造器直接反射得到代理对象,由于生成的代理类继承了Proxy,所以调用的是下面展示的构造方法
          return cons.newInstance(new Object[]{h});
          //......
      }

Proxy内部给生成代理类调用的构造方法

protected InvocationHandler h; // 属性
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    // 把传进来的InvocationHandler实现类,赋值给全局变量h。
    this.h = h;
}

当h全局属性被赋值后,代理生成的equals()方法里面super.h.invoke(this, m1, new Object[]{var1}),这里h就有值了。

  • getProxyClass0()方法,然后在get方法中调用了apply方法,完成对代理类的创建。

    image-20200927001703454.png
  • proxyClassCache属性是Proxy类的一个静态变量,被赋值为WeakCache类创建的实例

    //WeakCache类的get()方法
    public V get(K key, P parameter) {
           // ......
            while (true) {
                if (supplier != null) {
                    // supplier might be a Factory or a CacheValue<V> instance
                    // 重点方法,调用内部类Factory类的get方法
                    V value = supplier.get();
                    if (value != null) {
                        // 返回代理对象的Class对象
                        return value;
                    }
                }
            //......
            }
        }
  • 内部类Factory类的get()方法

    public synchronized V get() { // serialize access
        // ......
        V value = null;
        try {
            // 得到value的地方,调用Proxy的内部类 ProxyClassFactory 的apply方法
            value = Objects.requireNonNull(valueFactory.apply(key, parameter));
        } finally {
            if (value == null) { // remove us on failure
                valuesMap.remove(subKey, this);
            }
        }
        // ......
        return value;
    }
  • 调用Proxy的内部类 ProxyClassFactory 的apply方法

    apply方法

    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        // 循环所有接口
        for (Class<?> intf : interfaces) {
            /*
             * Verify that the class loader resolves the name of this
             * interface to the same Class object.
             */
            Class<?> interfaceClass = null;
            try {
                // 判断两个类相等的前提:是否是同一个类加载器加载的
                // 这里的interfaceClass 和循环的intf是否相等。在下面的if判断。
                // 这里用传来的loader重新得到接口的Class类对象
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            // 不相等,说明intf加载器不是和传来的loader不是一个。
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }
            /*
             * Verify that the Class object actually represents an
             * interface.
             */
            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }
            /*
             * Verify that this interface is not a duplicate.
             */
            if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
        }
        // 代理对象的包名
        String proxyPkg = null;     // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    
        /*
         * 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.
         */
        // 验证interface类的修饰符
        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 + ".";
        }
    
        /*
         * Choose a name for the proxy class to generate.
         */
        // 这个的num,是调用原子类自增,防止并发时的名字相同
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
        /*
         * Generate the specified proxy class.
         */
        // 下面是生成一个指定的代理类的生成字节码
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            // 根据字节码生成代理类的类对象(也就是Class对象),这是个native方法
            // 这里就直接返回到 Proxy.newProxyInstance()方法里面调用的getProxyClass0()方法
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        }
        // ......
    }

其中最重要的两个方法

generateProxyClass()通过反射收集字段和属性然后生成字节

defineClass0() jvm内部完成对上述字节的load

image-20200805224240498.png

总结:相比于JDK动态代理,cglib是通过继承来操作子类的字节码生成代理类,JDK是通过接口,然后利用java反射完成对类的动态创建,严格意义上来说cglib的效率高于JDK的反射,但是这种效率取决于代码功力,其实可以忽略不计,毕竟JDK是JVM的亲儿子........

Last Modified: October 10, 2020
Leave a Comment

4 Comments
  1. 蚩尤 蚩尤     Windows 10 /    Google Chrome

    大佬带带我

  2. Spoience Spoience     Android /    Google Chrome

    等我把C语言学好也来学java(我好菜@(你懂的))

    1. 未央花 未央花     Windows 10 /    QQ浏览器

      @Spoience可以::quyin:feizao::,深入学Java时,能懂C++就更好了(刚好你也会@(滑稽))(快更新!::quyin:witty::)

    2. Spoience Spoience     Windows 10 /    Google Chrome

      @未央花C++皮毛都没学到,就勉强过考试还是老师捞的@(笑尿)