美文网首页RxJavaJava服务器端编程编程语言爱好者
自定义SPI和热部署技术破坏类加载器的双亲委派模式

自定义SPI和热部署技术破坏类加载器的双亲委派模式

作者: 迦叶_金色的人生_荣耀而又辉煌 | 来源:发表于2020-12-08 07:20 被阅读0次

    上一篇 <<<ClassLoader类加载器顺序Demo测试与双亲委派源码解读
    下一篇 >>>JVM中对象如何完成空间分配和初始化工作


    1.SPI破解类加载器的双亲委派模式

    Java SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
    实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制.

    a、项目结构图

    b、演示效果

            //1.不设置,或设置当前线程的类加载器为APP加载器,则load正常读取
    //        Thread.currentThread().setContextClassLoader(SPITest.class.getClassLoader());
            //2.设置当前线程的加载类为扩展加载,则load读取不到,因为扫不到包
    //        Thread.currentThread().setContextClassLoader(SPITest.class.getClassLoader().getParent());
            //3.设置当前线程的加载类为启动类,则load可正常读取
    //        Thread.currentThread().setContextClassLoader(SPITest.class.getClassLoader().getParent().getParent());
    //
            ServiceLoader<JaryeService> load = ServiceLoader.load(JaryeService.class);
            Iterator<JaryeService> iterator = load.iterator();
            while (iterator.hasNext()) {
                JaryeService next = iterator.next();
                System.out.println(next + "\t" + next.getName());
            }
    

    c、源码解析

    从源码中可以看到使用此方式不存在先执行parent的情况,即可破解了双亲委派模式。



    数据库连接方式的底层实现也是此方式:
    Connection root = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8","root", "root");

    DriverManager的初始化方法:loadInitialDrivers()
    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    Iterator<Driver> driversIterator = loadedDrivers.iterator();
    

    2.自定义类加载器的方式破坏双亲委派模式(热部署)

    a、继承ClassLoader
    b、重写findClass方法,其中调用父类的defineClass方法

    public class JaryeClassLoader extends ClassLoader {
        /**
         * 重写findClass方法
         * @param name
         * @return
         */
        @Override
        public Class<?> findClass(String name) {
            try {
                byte[] data = getClassFileBytes(this.fileObject);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    
    public class ManualHotDeployment {
        private static long startTime;
    
        public static void main(String[] args) throws IllegalAccessException, InstantiationException {
            // 通过自定义类加载器,读取信息
            JaryeClassLoader jaryeClassLoader = new JaryeClassLoader();
            jaryeClassLoader.setFileObject(new File("/Users/jiangjinrong/Downloads/OrderServiceImpl.class"));
            Class<?> aClass = jaryeClassLoader.findClass("com.jarye.classloader.service.OrderServiceImpl");
            OrderService orderService = (OrderService) aClass.newInstance();
            System.out.println("读取class成功:" + orderService.addOrder());
            File fileObject = jaryeClassLoader.getFileObject();
            startTime = fileObject.lastModified();
            // 定时轮询读取信息并通过类加载器更新
            new Thread(() -> {
                while (true) {
                    try {
                        Thread.sleep(2000);
                    } catch (Exception e) {
    
                    }
                    long endTime = fileObject.lastModified();
                    if (endTime != startTime) {
    
                        System.out.println("class文件发生了变化,需要重新读取");
                        try {
                            JaryeClassLoader newMayiktClassLoader = new JaryeClassLoader();
                            newMayiktClassLoader.setFileObject(new File("/Users/jiangjinrong/Downloads/OrderServiceImpl.class"));
                            Class<?> newClass = newMayiktClassLoader.findClass("com.jarye.classloader.service.OrderServiceImpl");
                            OrderService newOrderService = (OrderService) newClass.newInstance();
                            System.out.println("读取class成功:" + newOrderService.addOrder());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        startTime = endTime;
                    }
                }
            }).start();
        }
    }
    

    热部署与热加载的区别

    热部署的案例:SpringBoot、tomcat、jetty

    相同点:
    a.不重启服务器编译/部署项目
    b.基于Java的类加载器实现

    不同点:
    a、部署方式
    热部署在服务器运行时重新部署项目
    热加载在运行时重新加载class
    b、实现原理
    热部署直接重新加载整个应用
    热加载在运行时重新加载class
    c、使用场景
    热部署更多的是在生产环境使用
    热加载则更多的实在开发环境使用


    相关文章链接:
    <<<JVM整体内存结构的图解,直观明了
    <<<javap命令查看对象信息及操作方法在JVM层的实现原理
    <<<javap命令反查汇编指令汇总
    <<<ClassLoader类加载器顺序Demo测试与双亲委派源码解读
    <<<JVM中对象如何完成空间分配和初始化工作
    <<<JVM元空间(方法区)和栈内存溢出原因及解决方案
    <<<JVM堆内存溢出和内存泄露问题定位和解决
    <<<JVM常见死锁问题产生原因和多种诊断方式
    <<<服务器CPU飙升为100%问题排查及如何避免
    <<<JVM内存诊断命令和排查工具汇总
    <<<JVM新生代老年代算法汇总图解
    <<<JVM垃圾回收不要手动System.gc的真正原因
    <<<JVM垃圾回收引用计数法和根搜索算法图解
    <<<JVM垃圾回收STW(Stop-The-World)代码演示
    <<<JVM垃圾回收器的发展历程及使用场景汇总
    <<<JVM串行并行垃圾回收器的关注点
    <<<一张图看懂CMS垃圾回收器的底层原理
    <<<G1能作为JDK9默认垃圾回收器的优势分析
    <<<CMS和G1的漏标问题解决及三色标记算法图解
    <<<GC中新生代进入老年代的方式汇总
    <<<GC常用日志参数配置及分析工具说明
    <<<FullGC、MinorGC、STW等常见问题如何解答
    <<<JVM性能调优的评估指标及调优示例

    相关文章

      网友评论

        本文标题:自定义SPI和热部署技术破坏类加载器的双亲委派模式

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