美文网首页
大厂高级工程师面试系列:Java动态代理机制和实现原理详解

大厂高级工程师面试系列:Java动态代理机制和实现原理详解

作者: 攻城狮Chova | 来源:发表于2021-07-14 12:49 被阅读0次

    代理模式

    • Java动态代理运用了设计模式中常用的代理模式
    • 代理模式:
      • 目的就是为其他对象提供一个代理用来控制对某个真实对象的访问
    • 代理类的作用:
      • 为委托类预处理消息
      • 过滤消息并转发消息
      • 进行消息被委托类执行后的后续处理


        在这里插入图片描述

        通过代理层这一中间层,有效的控制对于真实委托类对象的直接访问,同时又可以实现自定义的控制策略,比如Spring中的AOP机制,这样使得在设计上获得更大的灵活性

    • 代理的基本构成:
      在这里插入图片描述
    • 代理模式中有Subject角色 ,RealSubject角色和Proxy角色:
      • Subject: 负责定义RealSubjectProxy角色应该实现的接口
      • RealSubject: 用来真正完成业务服务功能
      • Proxy: 负责将自身的Request请求,调用RealSubject对应的request功能实现业务功能,自身不做真正的业务
    • 静态代理模式:
      • 当在代码阶段规定这种代理关系 ,Proxy类通过编译器编译成class文件,当系统运行时,此class已经存在
      • 这种静态代理模式可以访问无法访问的资源,增强现有的接口业务功能方面有很大的优点.但是大量的使用这种静态代理,会使系统内的类规模增大,并且不易维护
      • 由于ProxyRealSubject的功能本质上是相同的 ,Proxy只是中介的作用,这种代理在系统中的存在,会造成代码冗余
    • 为了解决静态代理模式的问题,就有了动态创建Proxy:
      • 在运行状态中,需要代理的地方,根据SubjectRealSubject, 动态地创建一个Proxy
      • Proxy使用完之后,就会销毁,这样就可以避免Proxy角色的class在系统中的冗余问题

    Java动态代理

    • java.lang.reflect.Proxy:
      • Java动态代理机制的主类
      • 提供一组静态方法为一组接口动态的生成对象和代理类
    // 该方法用于获取指定代理对象所关联的调用处理器
    public static InvocationHandler getInvocationHandler(Object proxy);
    
    // 该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
    public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces); 
    
    // 该方法用于判断指定类对象是否是一个动态代理类
    public static boolean isProxyClass(Class<?> cl);
    
    // 该方法用于为指定类装载器,一组接口以及调用处理器生成动态代理类实例
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
    
    • java.lang.reflect.InvocationHandler:
      • 调用处理器接口,自定义invoke方法用于实现对真正委托类的代理访问
    /**
     * 该方法负责集中处理动态代理类上的所有方法调用
     * 
     * @param proxy 代理实例
     * @param method 被调用的方法对象
     * @param args 调用参数
     * @return 返回调用处理器根据三个参数进行预处理或者分派到委托类实例上反射执行的对象
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
    
    • java.lang.ClassLoader:
      • 类装载器
      • 将类的字节码装载到Java虚拟机即JVM中,并为其定义类对象,然后该类才能被使用
      • Proxy类与普通类的唯一区别就是 :Proxy类字节码是由JVM在运行时动态生成的而不是预存在于任何一个.calss文件中
      • 每次生成动态代理类对象时都需要指定一个类装载器对象

    Java动态代理机制

    Java动态代理创建对象的过程:

    • 通过实现InvocationHandler接口创建自己的调用处理器
    /* 
     * InvocationHandlerImpl实现了InvocationHandler接口
     * 并能实现方法调用从代理类到委托类的分派转发向委托类实例的引用,用于真正执行分派转发过来的方法调用
     */
     InvocationHandler handler = new InvocationHandlerImpl(...);
    
    • 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类
    // 通过Proxy为包括Interface接口在内的一组接口动态创建代理类的类对象
    Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
    
    • 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
    // 通过反射从生成的类对象获得构造函数对象
    Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
    
    • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
    // 通过构造函数创建动态代理类实例
    Interface proxy = (Interface)constructor.newInstance(new Object[] { handler });
    

    为了简化对象创建过程 ,Proxy类中使用newInstanceProxy封装了步骤2-步骤4, 因此只需要两个步骤即可完成代理对象的创建

    // InvocationHandlerImpl实现了InvocationHandler接口,并能实现方法调用从代理类到委托类的分派转发
    InvocationHandler handler = new InvocationHandlerImpl(...); 
    // 通过 Proxy 直接创建动态代理类的实例
    Interface proxy = (Interface)Proxy.newProxyInstance(classLoader, new Class[] { Interface.class }, handler);
    

    Java动态代理注意点

    • 包:
      • 代理接口是public, 则代理类被定义在顶层包 ,package为空,否则default, 代理类被定义在该接口所在的包
    /*
     * 记录非公共代理接口的包,以便在同一个包中定义代理类
     * 验证所有非公共代理接口是否都在同一个包中 
     */
    for (int i =0; i < interfaces.length; i++ ) {
        int flags = interfaces[i].getModifiers();
        if (!Modifier.isPublic(flags)) {
            String name = interfaces[i].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 packaes");
            }
        }
    }
            if (proxyPkg == null) {
            // 没有使用非公共代理接口代理类的包
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }
    
    • 生成的代理类为public final,不能被继承
    • 类名的格式为 :"$ProxyN"
      • N是逐一递增的数字,代表Proxy是被第N次动态代理生成的代理类
      • 对于同一组接口,接口的排列顺序也相同,不会重复创建动态代理类,而是返回一个先前已经创建并缓存了的代理类对象,以此提高效率
    synchronized (cache) {
        /*
         * 不必担心获取到清除掉弱引用的缓存
         * 因为如果一个代理类已经被垃圾回收,代理类的类加载器也会被垃圾回收
         * 所以获取到的缓存都是加载到缓存中的映射
         */
         do {
            Object value = cache.get(key);
            if (value instanceof Reference) {
                proxyClass = (Class) ((Reference) value).get();
                if (proxyClass != null) {
                    /*
                     * 代理类已经生成,返回代理类
                     */
                    return proxyClass;
                } else if (value == pendingGenerationmarker) {
                    /*
                     * 代理类正在生成,等待代理类生成
                     */
                    try {
                        cache.wait();
                    } catch (InterruptedException e) {
                        /*
                         * 等待生成的代理类有一个极小的限定的时间,因此可以忽略线程在这里的影响
                         */
                    }
                    continue;
                } else {
                    /*
                     * 如果没有这个接口列表已经生成或者正在生成的代理类
                     * 需要去生成这些接口的代理类,将这些接口标记为待生成
                     */
                     cache.put(key, pendingGenerationMarker);
                     break;
                }
            }while (true);
         }
    
    • 类继承关系:
      在这里插入图片描述
      Proxy类是父类,这个规则适用于所有由Proxy创建的动态代理类(这也导致Java动态代理的缺陷,由于Java不支持多继承,所以无法实现对class的动态代理,只能对于Interface进行代理),该类实现了所有代理的一组接口,所以Proxy类能够被安全地类型转换到其所代理的某接口
    • 代理类的根类java.lang.Object中的hashCode(),equals()和().toString方法同样会被分派到调用处理器invoke方法执行

    Java动态代理测试

    创建一个动态代理类
    public class serviceProxy implements InvocationHandler {
        private Object target;
        /**
         * 绑定委托对象并返回一个代理对象
         * @param target 真实对象
         * @return 代理对象
         */
        public Object bind(Object target, Class[] interfaces) {
            this.target = target;
            // 取得代理对象
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    
        /**
         * 通过代理对象调用方法首先进入这个方法
         * @param proxy 代理对象
         * @param Method 方法,被调用方法
         * @param args 方法的参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /*
             * JDK动态代理
             */
             Object result = null;
             // 反射方法前调用
             System.err.println("--反射方法前执行的方法--");
             // 反射执行方法,相当于调用target.xXX()
             result = method.invoke(target, args);
             // 反射方法后调用
             System.err.println("--反射方法后执行的方法--");
             return result;
        }
    }
    
    • bind方法:
      • bind方法中的newProxyInstance方法,就是生成一个代理对象
        • 第一个参数: 类加载器
        • 第二个参数: 真实委托对象所实现的接口. 代理对象挂在那个接口下
        • 第三个参数: this代表当前HelloServiceProxy类, 也就是使用HelloServiceProxy作为对象的代理
    • invoke方法:
      • invoke方法有三个参数:
        • 第一个proxy是代理对象
        • 第二个是当前调用那个方法
        • 第三个是方法的参数
    ProxyTest
    public class ProxyTest {
        public static void main(String[] args) {
            HelloServiceProxy proxy = new HelloServiceProxy();
            HelloService service = new HelloServiceImpl();
            // 绑定代理对象
            service = (HelloService) proxy.bind(service, new Class[] {HelloService.class});
            service.sayHello("user");
        }
    }
    

    class文件分析

    • Java编译器编译好Java文件后,产生 .class文件在磁盘中:
      • class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码
      • JVM虚拟机读取字节码文件,取出二进制数据
      • 加载到内存中,解析 .class文件内的信息,生成对应的Class对象
        在这里插入图片描述
    • 加载class文件字节码到系统内,转换成class对象,然后再实例化:
      • 定义一个类
      • 自定义一个类加载器,用于将字节码转换成class对象
      • 编译 .class文件,在程序中读取字节码,然后转换成相应的class对象,再实例化

    在运行期生成二进制字节码

    • 在代码中,动态创建一个类:
      • 由于JVM通过字节码的二进制信息加载类,因此在运行期的系统中,遵循Java编译系统组织 .class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类
        在这里插入图片描述
    • 可以使用开源框架在运行时期按照Java虚拟机规范对class文件的组织规则生成对应的二进制字节码. 比如ASM,Javassist

    ASM

    • ASM是一个Java字节码操控框架:
      • 能够以二进制形式修改已有类或者动态生成类
      • ASM在创建class字节码的过程中,操纵的级别是底层JVM汇编指令级别
      • ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为
      • ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户的要求生成新类
    • 通过ASM生成类的字节码:
      • 使用ASM框架提供的ClassWriter接口,通过访问者模式进行动态创建class字节码
      • 然后使用Java反编译工具 (JD_GUI) 打开硬盘上生成的类.class文件查看类信息
      • 再使用定义的类加载器将class文件加载到内存中,然后创建class对象,并且实例化一个对象,调用code方法,查看code方法中的结果
    • 至此表明: 在代码里生成字节码,并动态地加载成class对象,创建实例是完全可以实现的

    Javassist

    • Javassist是一个开源的分析,编辑和创建Java字节码的类库,已经加入JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架:
      • Javassist是JBoss一个子项目,主要优点在于简单快速
      • 直接使用Java编码的形式,不需要虚拟机指令,就能改变类的结构或者动态生成类

    源码分析

    Proxy类
    // 映射表: 用于维护类装载器对象到其对应的代理类缓存
    private static Map loaderToCache = new WeakHashMap();
    
    // 标记: 用于标记一个动态代理类正在被创建中
    private static Object pendingGenerationMarker = new Object();
    
    // 同步表: 记录已经被创建的动态代理类类型,主要通过方法isProxyClass进行相关判断
    private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap());
    
    // 关联的调用处理器引用
    protected InvocationHandler h;
    
    newProxyInstance
    • Proxy静态方法newProxyInstance:
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
        /*
         * 检查关联调用处理器是否为空,如果是空则抛出异常
         */
         if (h == null) {
            throw new NullPointerException();
         }
         /*
          * 获得与指定类型装载器和一组接口相关的代理类类型对象
          */
          Class<?> cl = getProxyClass0(loader, interfaces);
          /*
           * 通过反射获取构造函数对象并生成代理类实例
           */
           try {
               final Constructor<?> cons = cl.getConstructor(constructorParams);
               final  InvocationHandler ih = h;
               SecurityManager sm = System.getSecurityManager();
               if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                   /* 
                    * 使用doPrivilege创建动态代理类实例
                    * 因为代理类实现可能需要特殊权限的非公共接口
                    */
                    return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                        public Object run() {
                            return newInstance(cons, ih);
                        }   
                    });
               } else {
                    return newInstance(cons, ih);
               }
           } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString());
           }
    }
    
    private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
        try {
            return cons.newInstance(new Object[] {h});
        } catch (IllegalAccessException e) {
            throw new InternalError(e.toString());
        } catch (InstantationException e) {
            throw new InternalException(e.toString());
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalException(e.toString());
            }
        }
    }
    
    • 动态代理的真正的关键是在getProxyClass0() 方法

    getProxyClass0方法分析

    • 通过getProxyClass0方法中生成具体的class文件的过程:
      • 定义path
      • class文件写到指定的硬盘中
      • 反编译生成的class文件

    getProxyClass0() 方法分为四个步骤:

    1. 对这组接口进行一定程度的安全检查:
      1.1 接口类对象是否对类装载器可见
      1.2 接口类对象与类装载器所识别的接口类对象完全相同
      1.3 确保是interface类型而不是class类型.
    for (int i = 0; i < interfaces.length; i++ ) {
        /*
         * 验证类加载器是否将此接口的名称解析为相同的类对象
         */
         String interfaceName = interface[i].getName();
         Class interfaceClass = null;
         try {
            /*
             * forName(String name, boolean initialize, ClassLoader loader)
             * Returns the Class object associated with the class or interface with the given string name,
             * using the given class loader
             */ 
            interfaceClass = Class.forName(interfaceName, false, loader);
         } catch (ClassNotFoundException e) {
         }
         if (interfaceClass != interface[i]) {
            throw new IllegalArgumentException(interface[i] + "is not visible from class loader.");
         }
    
        /*
         * 验证类加载器得到的类对象是否是interface类型
         */
         if (! interfaceClass.isInterface()) {
            throw new IllegalArgumentException(interfaceClass.getName() + "is not an interface.");
         }
    
        /*
         * 验证类加载器得到的类对象接口不是一个重复的接口
         */
         if (interfaceSet.contains(interfaceClass)) {
            throw new IllegalArgumentException("repeated interface:" + interface.getName());
         }
         interfaceSet.add(interfaceClass);
         interfaceName[i] = interfaceName;
    }
    
    1. loaderToCache映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在,就会创建一个新的缓存表并更新到loaderToCahe中:
      2.1 loaderToCache存放键值对 : 接口名字列表:动态生成的代理类的类对象的引用
      2.2 当代理类正在被创建时,会临时进行保存 : 接口名字列表:pendingGenerationMarker
      2.3 标记pendingGenerationMarker的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成
    /*
     * 寻找类加载器的缓存表,如果没有就为类加载器创建代理类缓存
     */
    Map cache;
    synchronized (loaderToCache) {
        cache = (Map) loaderToCache.get(loader);
        if (cache == null) {
            cache = new HashMap();
            loaderToCache = put(loader, cache);
        }
    }
    do {
        /* 
         * 以接口名字作为关键字获得对应的cache值
         */
         Object value = cache.get(key);
         if (value instanceof Reference) {
            proxyClass = (Class)((Reference)value).get();
         }
         if (proxyClass != null) {
            // 如果已经创建,直接返回
            return proxyClass;
         } else if (value == pendingGenerationMarker) {
            // 代理类正在创建,保持等待
            try {
                cache.wait()
            } catch (InterruptException e) {
            }
            // 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
            continue;
         } else {
            // 标记代理类正在被创建
            cache.put(key, pendingGenerationMarker);
            // 跳出循环已进入创建过程
            break;
         }
    } while(true)
    
    1. 动态创建代理类的class对象
    /* 
     * 选择一个名字代理类来生成
     */
    long num;
    synchronized (nextUniqueNumberLock) {
        num = nextUniqueNumber ++;
    }
    String proxyName = proxyPkg + proxyClassNamePrefix + num;
    /*
     * 验证类加载器中没有使用这个名字定义的类
     */
     ...
     
    // 动态生成代理类的字节码数组
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
    try {
        // 动态地定义新生成的代理类
        proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
    } catch (ClassFormatError e) {
        /*
         * 这里的类格式错误指的是生代理类代码中的错误
         * 还有一些应用到代理类生成的参数的错误,比如一些虚拟机限制的超量
         */
         throw new IllegalArgumentException(e.toString());
    }
    // 将生成的代理类对象记录到proxyClasses中
    proxyClasses.put(proxyClass, null);
    

    首先根据接口public与否的规则生成代理类的名称 - $ProxyN格式,然后动态生成代理类. 所有的代码生成工作由ProxyGenerator完成,该类在rt.jar中,需要进行反编译

    public static byte[] generateProxyClass(final String name, Class[] interfaces) {
        ProxyGenerator gen = new ProxyGenerator(name, interfaces);
        // 动态生成代理类的字节码
        final byte[] classFile = gen.generateClassFile();
        // 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上
        if (saveGeneratedFiles) {
            java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction<Void>() {
                    public Void run() {
                        try{
                            FileOutputStream file = new FileOutputStream(doToSlash(name) + ".class");
                            file.write(classFile);
                            file.close();
                            return null;
                        } catch (IOException e) {
                            throw new InternalError("I/O exception saving generated file :" + e);
                        }
                    }
                }
            );
        } 
        // 返回代理类的字节码
        return classFile;
    }
    
    1. 代码生成过程进入结尾部分,根据结果更新缓存表. 如果代理类成功生成则将代理类的类对象引用更新进缓存表,否则清除缓存表中对应的关键值,最后唤醒所有可能的正在等待的线程
    finally {
        synchronized (cache) {
            if (proxyClass != null) {
                cache.put(key, new WeakReference(proxyClass));
            } else {
                cache.remove(key);
            }
            cache.notifyAll();
        }
    }
    return proxyClass;
    

    InvocationHandler解析

    • Proxy角色在执行代理业务的时候,就是在调用真正业务之前或者之后完成一些额外的功能
      在这里插入图片描述
    • 代理类就是在调用真实角色的方法之前或者之后做一些额外的业务
    • 为了构造具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发管理器,让这个管理器统一管理触发,这个触发管理器就是InvocationHandler
    • 动态代理工作的基本工作模式:
      • 将方法功能的实现交给InvocationHandler角色
      • 外接对Proxy角色中每一个的调用 ,Proxy角色都会交给InvocationHandler来处理
      • InvocationHandler则调用具体对象角色的方法
        在这里插入图片描述
    • 在这种模式中,代理ProxyRealSubject应该实现相同的类的public方法,有两种方式:
      • 一个比较直观的方式: 就是定义一个功能接口,然后让ProxyRealSubject来实现这个接口 (JDK中的动态代理机制 - Java动态代理机制)
      • 比较隐晦的方式: 通过继承实现Proxy继承RealSubject. 因为Proxy继承自RealSubject, 这样Proxy则拥有了RealSubject的功能 ,Proxy还可以通过重写RealSubject中的方法来实现多态(cglib)

    JDK动态代理机制

    • JDK动态代理机制通过接口为RealSubject创建一个动态代理对象:
      • 获取RealSubject上的所有接口列表
      • 确定要生成的代理类类名
      • 根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码
      • 将对应的字节码转换成对应的class对象
      • 创建InvocationHandler, 用来处理Proxy所有方法调用
      • Proxyclass对象,以创建的handler为参数,实例化一个proxy
    • JDK动态代理实例:
      • 定义两个接口Vehicle和Rechargeable
      • Vehicle接口表示交通工具类,有drive()方法
      • Rechargeable接口表示可充电,有recharge()方法
      • 定义一个实现两个接口的类ElectricCar,类图如下:


        在这里插入图片描述
    • 创建ElectricCar的动态代理类:
    /**
     * 交通工具接口
     */
     public interface Vehicle {
      public void drive();
     }
    
    /**
     * 充电接口
     */
     public interface Rechargable {
      public void recharge();
     }
    
    /**
     * 电动车类
     * 实现Rechargable, Vehicle接口
     */
     public class ElectricCar implements Rechargable, Vehicle {
      @Override
      public void drive() {
          System.out.println("ElectricCar can drive.");
      }
     
      @Override
      public void recharge() {
          System.out.println("ElectricCar can recharge.");
      }
     }
    
    /**
     * 动态代理类的触发器
     */
     public class InvocationHandlerImpl implements InvocationHandler {
      private ElectricCar car;
      
      public InvocationHandlerImpl(Electric car) {
          this.car = car;
      } 
     
      @Override
      public Object invoke(Object paramObject, Method paramMethod, Object[] paramArrayOfObject) throws Throwable {
          System.out.println("正在调用方法:" + paramMethod.getName() + "...");
          paramMethod.invoke(car, null);
          System.out.println("方法" + paramMethod.getName() + "调用结束.");
          return null;
      }
     }
    
    public class ProxyTest {
      public static void main(String[] args) {
          ElectricCar car = new ElectricCar();
          // 获取对应的ClassLoader
          ClassLoader classLoader = car.getClass().getClassLoader();
          // 获取ElectricCar所实现的所有接口
          Class[] interfaces = car.getClass().getInterfaces();
          // 设置一个来自代理传过来的方法调用请求处理器,处理所有的代理对象上的方法调用
          InvocationHandler handler = new InvocationHandlerImpl(car);
          /*
           * 创建代理对象在这个过程中:
           *      a. JDK根据传入的参数信息动态地在内存中创建和 .class 等同的字节码
           *      b. 根据相应的字节码转换成对应的class
           *      c. 然后调用newInstance()创建实例
           */
          Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
          Vehicle vehicle = (Vehicle) o;
          vehicle.drive();
          Rechargable rechargable = (Rechargable) o;
          rechargable.recharge();
      }
    }
    
    • 生成动态代理类的字节码并且保存到硬盘中:
    • JDK提供了sun.misc.ProxyGenerator.generateProxyClass(String proxyName, calss[] interfaces) 底层方法来产生动态代理类的字节码
    • 定义一个工具类,用来将生成的动态代理类保存到硬盘中:
    public class proxyUtils {
      /*
         * 根据类信息,动态生成二进制字节码保存到硬盘中
         * 默认是clazz目录下
         * 
         * @params clazz 需要生成动态代理类的类 
         * @proxyName 动态生成代理类的名称
         */
         public static void generateClassFile(Class clazz, String proxyName) {
          // 根据提供的代理类名称和类信息,生成字节码
          byte[] classFile = ProxyGenerator.generateProxyClass(ProxyName, clazz.getInterfaces());
          String paths = clazz.getResource(".").getPath();
          System.out.println(paths);
          FileOutputStream out = null;
          try {
              // 保留到硬盘中
              out = new FileOutputStream(paths + proxyName + ".class");
              out.write(classFile);
              out.flush();
          } catch (Exception e) {
              e.printStackTrace();
          } finally {
              try {
                  out.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
         }
    }
    
    • 修改代理类名称为 "ElectricCarProxy", 并保存到硬盘,使用以下语句:
    ProxyUtils.generateClassFile(car.getClass(), "ElectricCarProxy");
    

    这样将在ElectricCar.class同级目录下产生ElectricCarProxy.class文件

    • 使用反编译工具jd-gui.exe打开,将会看到以下信息:
    /**
     * 生成的动态代理类的组织模式是继承Proxy类,然后实现需要实现代理的类上的所有接口
     * 在实现过程中,是通过将所有的方法都交给InvocationHandler来处理
     */
     public final class ElectricCarProxy extends Proxy implements Rechargable,Vehicle {
      private static Method m1;
      private static Method m3;
      private static Method m4;
      private static Method m0;
      private static Method m2;
    
      public ElectricCarProxy(InvocationHandler paramInvocationHandler) throws {
          super(paramInvocationHandler);
      }  
     
      public final boolean equals(Object paramObject) throws {
          try {
              /*
               * 方法功能的实现交给InvocationHandler处理
                 */
                 return ((Boolean) this.h.invoke(this, m1, new Object[] {paramObject})).booleanValue();
          } catch (Error | RuntimeException localError) {
              throw localError;
          } catch (Throwable localThrowable) {
              throw new Undeclared ThrowableException(localThrowable);
          }
      } 
      
      public final void recharge() throws {
          try {
              /*
               * 方法功能的实现交给InvocationHandler处理
                  */
                 this.h.invoke(this, m3, null);
                 return;           
          } catch (Error | RuntimeException localError) {
              throw localError;
          } catch (Throwable localThrowable) {
              throw new Undeclared ThrowableException(localThrowable);
          }
      }
      
          public final drive() throws {
              try {
                  /* 
                   * 方法实现交给InvocationHandler处理
                   */
                  this.h.invoke(this, m4, null);
                  return;
              } catch (Error | RuntimeException localError) {
                  throw localError;
              } catch (Throwable localThrowable) {
                  throw new Undeclared ThrowableException(localThrowable);
              }
          }
       
          public final int hasCode() throws {
              try {
                  /*
                   * 方法功能交给InvocationHandler处理
                   */
                  return ((Integer) this.h.invoke(this, m0, null)).intValue();
              } catch (Error | RuntimeException localError) {
                  throw localError;
              } catch (Throwable localThrowable) {
                  throw new Undeclared ThrowableException(localThrowable);
              }
          } 
       
          public final String toString() throws {
              try {
                  /*
                   * 方法功能实现交给InvocationHandler处理
                   */
                  return (String)this.h.invoke(this, m2, null);
              } catch (Error | RuntimeException localError) {
                  throw localError;
              } catch (Throwable localThrowable) {
                  throw new Undeclared ThrowableException(localThrowable);
              }
          }
       
        static {
              try {
                  /*
                   * 为每一个需要方法对象 
                   * 当调用相应的方法时,分别将方法对象作为参数传递给InvocationHandler处理
                   */
                  m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
                  m3 = Class.forName("com.oxford.proxy.Rechargable").getMethod("recharge", new Class[0]);
                  m4 = Class.forName("com.oxford.proxy.Vehicle").getMethod("drive", new Class[0]);
                  m0 = Class.forName("java.lang.Object").getMethod("hasCode", new Class[0]);
                  m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                  return;
                  } catch (NoSuchMethodException localNoSuchMethodException) {
                      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
                  } catch (ClassNotFoundException localClassNotFoundException) {
                      throw new NoClassDefFoundError(localClassNotFoundException.getMessge());
                  }
          }
     }
    
    • 生成的动态代理类的特点:
      • 继承自java.lang.reflect.Proxy, 实现了Rechargable,Vehicle这两个ElectricCar接口
      • 类中的所有方法都是final
      • 所有的方法功能的实现都统一调用了InvocationHandlerinvoke() 方法
        在这里插入图片描述

    CGLIB动态代理机制

    • CGLIB通过类继承生成动态代理类
    • JDK动态代理类的特点:
      • 某个类必须有实现的接口,而生成的代理类只能代理某个类接口定以的方法. 这样会导致子类实现继承两个接口的方法外,另外实现的方法,在产生的动态代理类中不会有这个方法
      • 如果某个类没有实现接口,那么这个类就不能使用JDK动态代理了
    • CGLIB: Code Generation Library, CGLIB是一个强大的,高性能,高质量的Code生成类库,可以在运行时期扩展Java类与实现Java接口
    • CGLIB创建类的动态代理类的模式:
      • 查找类中所有非finalpublic类型的方法定义
      • 将这些方法的定义转换成字节码
      • 将组成的字节码转换成相应的代理的class对象
      • 实现MethodInterceptor接口,用来处理对代理类上的所有方法的请求 (类似JDK动态代理中的InvocationHandler的作用)
    • 定义一个Programmer类,一个Hacker
    /**
     * 开发人员类
     */
     public class Programmer {
      public void code() {
          System.out.println("开发人员开发程序代码.");
      }
     }
    
    /**
     * 黑客类
     * 实现了方法拦截器接口
     */
     public class Hacker implements MethodInterceptor {
      @Override
      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
          System.out.println("Hacker what to do.");
          proxy.invokeSuper(obj, args);
          System.out.println("Hacker done this.");
          return null;
      }
     }
    
    • 测试类:
    public class Test {
          public static void main(String[] args) {
              Programmer programmer = new Programmer();
              Hacker hacker = new Hacker();
              // CGLIB中的加强器,用来创建动态代理
              Enhancer enhancer = new Enhancer();
              // 设置要创建动态代理的类
              enhancer.setSuperclass(programmer.getClass());
              /*
               * 设置回调
               * 这里相当于对于代理类上所有方法的调用,都会调用CallBack
               * 而CallBack则需要实行intercept()方法进行拦截
               */
              enhancer.setCallBack(hacker);
              Programmer proxy = (Programmer) enhancer.create();
              proxy.code();
          }
    }
    
    • 通过CGLIB生成的class文件的内容:
    public class Programmer EnhancerByCGLIB fa7aa2cd extends Programmer implements Factory {
        /* 
         * ....
         */
         
         // Enhancer传入的methodInterceptor
         private MethodInterceptor CGLIB$CALLBACK_0;
         
         /*
          * ...
          */
    
          public final void code() {
            MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
            if (tmp4_1 == null) {
                tmp4_1;
                // 若callback不为空,则调用methodInterceptor的intercept()方法
                CGLIB$BIND_CALLBACKS(this);
            }
            if (this.CGLIB$CALLBACK_0 != null)
                return;
                // 如果没有设置callback回调函数,则默认执行父类的方法
                super.code();
          }
          /*
           * ...
           */
    }
    

    相关文章

      网友评论

          本文标题:大厂高级工程师面试系列:Java动态代理机制和实现原理详解

          本文链接:https://www.haomeiwen.com/subject/lmrypltx.html