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

上一篇 << 下一篇 >>>JVM中对象如何完成空间分配和初始化工作


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

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

a、项目结构图

自定义SPI和热部署技术破坏类加载器的双亲委派模式_第1张图片

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 load = ServiceLoader.load(JaryeService.class);
        Iterator iterator = load.iterator();
        while (iterator.hasNext()) {
            JaryeService next = iterator.next();
            System.out.println(next + "\t" + next.getName());
        }

c、源码解析

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


自定义SPI和热部署技术破坏类加载器的双亲委派模式_第2张图片

自定义SPI和热部署技术破坏类加载器的双亲委派模式_第3张图片

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

DriverManager的初始化方法:loadInitialDrivers()
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator 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、使用场景
热部署更多的是在生产环境使用
热加载则更多的实在开发环境使用


相关文章链接:
<< << << << << << << << <<<服务器CPU飙升为100%问题排查及如何避免
<< << << << << << << <<<一张图看懂CMS垃圾回收器的底层原理
<< << << << << <<

你可能感兴趣的:(自定义SPI和热部署技术破坏类加载器的双亲委派模式)