重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
这篇文章主要介绍Java基于自定义类加载器如何实现热部署,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!
目前创新互联已为上1000+的企业提供了网站建设、域名、虚拟空间、成都网站托管、企业网站设计、淳安网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。
热部署:
热部署就是在不重启应用的情况下,当类的定义即字节码文件修改后,能够替换该Class创建的对象。一般情况下,类的加载都是由系统自带的类加载器完成,且对于同一个全限定名的java类,只能被加载一次,而且无法被卸载。可以使用自定义的 ClassLoader 替换系统的加载器,创建一个新的 ClassLoader,再用它加载 Class,得到的 Class 对象就是新的(因为不是同一个类加载器),再用该 Class 对象创建一个实例,从而实现动态更新。如:修改 JSP 文件即生效,就是利用自定义的 ClassLoader 实现的。
还需要创建一个守护线程,不断地检查class文件是否被修改过,通过判断文件的上次修改时间实现。
演示:
原来的程序:
修改后重新编译:
代码:
package Dynamic; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.TimeUnit; public class ClassLoadStudy { public static void main(String[] args) throws Exception { HotDeploy hot = new HotDeploy("Dynamic.Task"); hot.monitor(); while (true) { TimeUnit.SECONDS.sleep(2); hot.getTask().run(); } } } // 热部署 class HotDeploy { private static volatile Runnable instance; private final String FILE_NAME; private final String CLASS_NAME; public HotDeploy(String name) { CLASS_NAME = name; // 类的完全限定名 name = name.replaceAll("\\.", "/") + ".class"; FILE_NAME = (getClass().getResource("/") + name).substring(6); // 判断class文件修改时间使用,substring(6)去掉开头的file:/ } // 获取一个任务 public Runnable getTask() { if (instance == null) { // 双重检查锁,单例,线程安全 synchronized (HotDeploy.class) { if (instance == null) { try { instance = createTask(); } catch (Exception e) { e.printStackTrace(); } } } } return instance; } // 创建一个任务,重新加载 class 文件 private Runnable createTask() { try { Class clazz = MyClassLoader.getLoader().loadClass(CLASS_NAME); if (clazz != null) return (Runnable)clazz.newInstance(); } catch (Exception e) { e.printStackTrace(); } return null; } // 监视器,监视class文件是否被修改过,如果是的话,则重新加载 public void monitor() throws IOException { Thread t = new Thread(()->{ try { long lastModified = Files.getLastModifiedTime(Path.of(FILE_NAME)).toMillis(); while(true) { Thread.sleep(500); long now = Files.getLastModifiedTime(Path.of(FILE_NAME)).toMillis(); if(now != lastModified) { // 如果class文件被修改过了 lastModified = now; instance = createTask(); // 重新加载 } } } catch (InterruptedException | IOException e) { e.printStackTrace(); } }); t.setDaemon(true); // 守护线程 t.start(); } } // 自定义的类加载器 class MyClassLoader extends ClassLoader { @Override public Class> findClass(String name) throws ClassNotFoundException { try { String fileName = "/" + name.replaceAll("\\.", "/") + ".class"; InputStream is = getClass().getResourceAsStream(fileName); byte[] b = is.readAllBytes(); return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } public static MyClassLoader getLoader() { return new MyClassLoader(); } }
遇到的坑:
刚开始自定义类加载器时,重写的是 loadClass(String name) 方法,但不断地报错,后来明白了,因为 Task 类实现了 Java.lang.Runnable 接口,且重写 loadClass 方法破坏了双亲委派机制,导致了自定义的类加载器去加载 java.lang.Runnable,但被Java安全机制禁止了所以会报错。defineClass调用preDefineClass,preDefineClass 会检查包名,如果以java开头,就会抛出异常,因为让用户自定义的类加载器来加载Java自带的类库会引起混乱。
于是又重写findClass 方法,但还是不行,findClass方法总是得不到执行,因为编译好的类是在 classpath 下的,而自定义的 ClassLoader 的父加载器是 AppClassLoader,由于双亲委派机制,类就会被 Application ClassLoader来加载了。因此自定义的 findClass 方法就不会被执行。解决方法是,向构造器 ClassLoader(ClassLoader parent) 传入null,或传入 getSystemClassLoader().getParent()。
还有就是路径问题:
path不以 / 开头时,默认是从此类所在的包下取资源;path 以 / 开头时,则是从ClassPath根下获取;
URL getClass.getResource(String path)
InputStream getClass().getResourceAsStream(String path)
getResource("") 返回当前类所在的包的路径
getResource("/") 返回当前的 classpath 根据路径
path 不能以 / 开始,path 是从 classpath 根开始算的, 因为classloader 不是用户自定义的类,所以没有相对路径的配置文件可以获取,所以默认都是从哪个classpath 路径下读取,自然就没有必要以 / 开头了 。
URL Class.getClassLoader().getResource(String path)
InputStream Class.getClassLoader().getResourceAsStream(String path)
Java是一门面向对象编程语言,可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序。
以上是“Java基于自定义类加载器如何实现热部署”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注创新互联行业资讯频道!