栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java基于自定义类加载器实现热部署过程解析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java基于自定义类加载器实现热部署过程解析

热部署:

热部署就是在不重启应用的情况下,当类的定义即字节码文件修改后,能够替换该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)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/134839.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号