博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JVM虚拟机笔记(3)-- 类加载器实践热部署
阅读量:4511 次
发布时间:2019-06-08

本文共 4915 字,大约阅读时间需要 16 分钟。

1 需求

spring boot项目通过上传jar包的方式,自动加载jar包中的类。应用场景:用户上传驱动实现,服务实现热部署。

2 实现过程

要实现热部署功能,我们首先需要实现两个基础功能:1 加载指定路径的class文件  2 指定路径下的文件有变动时,触发事件,重新加载

 

2.1 多jar包应用实现

2.1.1 类加载设计

1 需要加载其他路劲下的jar包,只能由代码自身来引入一个类加载器,指定加载某一路径下的jar包,并且在没有特殊情况下不破坏双亲委派机制。

2 ide上运行的程序,是使用的jvm默认的类加载机制,从main函数启动,但是spring boot的可运行jar包,是在原有的基础上再包装了一层启动配置。并引入了自己的类加载器来处理这个比较特殊的jar包。

(springboot类加载机制:,)

                                                                         图1  ide下类加载图和可执行jar包下类加载图

 

 

3 思考一下下图所示的类加载图能否实现需求?

                             图2 可执行jar包下破坏双亲委派的类加载图

 

说明:这类加载图是我们在实现热部署需求时,比较易出现的现象,需要先对spring boot可执行jar的类加载机制有个初步认识。

 

2.1.2 代码实践

获取加载指定路径的类加载器,特别注意:新建类加载器对象时,需要指定其父类加载器,如果不指定:classLoader = new URLClassLoader(urls),默认为appClassLoader,这样就会造成图2 所示的的情形

public class ClassLoaderContext {    public static String DEFAULT_PATH = "lib";    private static ClassLoader classLoader;    ....      public static ClassLoader getClassLoader() {        if (null == classLoader) {            String libPath = FileUtil.getDefaultPath(DEFAULT_PATH);            List
files = pathToJarFile(libPath); URL[] urls = fileToUrl(files);// classLoader = new URLClassLoader(urls); classLoader = new URLClassLoader(urls, ClassLoaderContext.class.getClassLoader()); } return classLoader; }}

 

由指定路径的类加载来加载class,调用业务逻辑

//获取类加载器        ClassLoader cloader = ClassLoaderContext.getClassLoader();        String result = null;        try {            Class clazz = cloader.loadClass("com.example.timyag.service.impl.TestServiceImpl");            result =  ((ITestService)clazz.newInstance()).sayHello(p1);        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        }        return result;

 

由于接口 ITestService类文件在两个jar包中对会存在(不同时存在的话,编译都不过了,不多说看代码)。所以在图2 这种类加载图下,双亲委派被破坏,ITestService类被加载了两次,分别被加载在LaunchedURLClassLoader和self ClassLoader里,导致代码  “((ITestService)clazz.newInstance())” 就会报错,因为实例对象(clazz.newInstance())不是LaunchedURLClassLoader加载的ITestService接口的实现类,强制转换失败。

 

2.2 路径监听

系统热部署需要感知路径下文件的变化,及时重新加载类文件。我们使用 JDK 1.7提供的WatchService,利用底层文件系统提供的功能

参考:

 

主要业务代码:

在监听到路径下文件有变化时,通知订阅者。

public class PathWatcher extends Observable {    @Getter    private String watchPath;    public long lastMod;    public PathWatcher(String watchPath) {        this.watchPath = watchPath;    }    public void doWatch() {        try {            final Path path = FileSystems.getDefault().getPath(watchPath);            final WatchService watchService = FileSystems.getDefault().newWatchService();            path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY);            boolean UPDATED = false;            while (true) {                final WatchKey wk = watchService.take();                for (WatchEvent
event : wk.pollEvents()) { WatchEvent.Kind
kind = event.kind(); if (kind == OVERFLOW) { continue; } Path changed = (Path) event.context(); Path absolute = path.resolve(changed); File configFile = absolute.toFile(); long lastModified = configFile.lastModified(); // 利用文件时间戳,防止触发两次 if (lastModified != lastMod && configFile.length() > 0) { // 保存上一次时间戳 lastMod = lastModified; UPDATED = true; } } if (UPDATED) { System.out.println("path file changed"); setChanged(); //通知订阅者 notifyObservers(); UPDATED = false; } // reset the key boolean valid = wk.reset(); if (!valid) { System.out.println("watch key invalid!"); break; } //10秒刷新一次 Thread.sleep(10 * 1000); } } catch (Exception e) { e.printStackTrace(); } }}

 

2.3 类重新加载

在更新了jar包之后,我们需要重新加载class类,实现起来很简单,只需要新建一个类加载器。

主逻辑:在接受到发布者发布的文件变更通知时,将静态的classLoader置为null。下载使用该类加载器时,就能新建一个对象。

public void reloadClassLoaderAndClass(String path) {        System.out.println("reload class");        classLoader = null;//        getNewClassLoader(path);    }    @Override    public void update(Observable o, Object arg) {        reloadClassLoaderAndClass(((PathWatcher) o).getWatchPath());    }

 

 

3 小结

git项目地址: 分支:hot_deploy_simple

后续优化点:

1 外部jar包类的使用,需要在主程序中指明类名(Class clazz = cloader.loadClass("com.example.timyag.service.impl.TestServiceImpl");),这个耦合性太大,参考jdbc的驱动引入方式,可以使用SPI机制,由外部jar包来指定实现类类名。

2 外部jar包类在使用时,不能总是实例化一个对象来使用, 希望能将实例对象注入到spring中,后续使用都有spring容器来管理,在热部署重新加载类时,又要及时更新注入的实例对象

3 如果热部署频率高,那么加载进的类会越来越多,我们需要及时的去卸载。但是我们没有办法直接控制它的卸载,只能到jvm触发GC的时候才会去卸载多余的类

 

 

 

 

 

转载于:https://www.cnblogs.com/timyag/p/10591929.html

你可能感兴趣的文章
python类之魔法方法
查看>>
International Conference for Smart Health 2015 Call for Papers
查看>>
面向对象编程的介绍:三大特征:封装性, 继承, 多态.
查看>>
netty是什么?
查看>>
微信小店二次开发功能套餐列表
查看>>
ubuntu常见错误--Could not get lock /var/lib/dpkg/lock解决
查看>>
Java中BIO,NIO,AIO的理解
查看>>
浅谈Sql各种join的用法
查看>>
Durid数据库连接池配置(不使用框架)
查看>>
BarCode128B字符转换函数(PB,SQL)
查看>>
watir学习资料
查看>>
Jmeter属性和变量
查看>>
java并发编程:并发容器之CopyOnWriteArrayList(转)
查看>>
python基础——面向对象进阶下
查看>>
Linux vi 命令详解
查看>>
本地如何搭建IPv6环境测试你的APP
查看>>
oracle、mysql新增字段,字段存在则不处理
查看>>
CAP原理
查看>>
动态加载主题
查看>>
leetcode[125]Valid Palindrome
查看>>