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)

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

(0)

相关推荐

  • java自定义封装StringUtils常用工具类

    自定义封装StringUtils常用工具类,供大家参考,具体内容如下 package com.demo.utils; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * 字符串操作工具类 * @author dongyangyang * @Date 2016/12/28 23:12 * @Version 1.0 * */ public class StringUtils { /** * 首字

  • Java抛出异常与自定义异常类应用示例

    本文实例讲述了Java抛出异常与自定义异常类.分享给大家供大家参考,具体如下: 异常处理常用方法: 常用的异常处理方法有: 一.try()catch() 语句 二.throw / throws 语句 三.自定义异常类 用途: 众所周知,当程序运行过程中,如果遇到了错误(比如数组下标越界,空指针等)程序就会自动终止,但这种错误一般是偶然的,可能是用户输入不呵呵程序编写者的意图导致,而不是程序本身问题,这是我们要做的,是让操作者知道发生了什么事情,而不是直截了当的结束程序,这时我们就用到了异常处理(

  • Java实现的自定义类加载器示例

    本文实例讲述了Java实现的自定义类加载器.分享给大家供大家参考,具体如下: 一 点睛 1 ClassLoader类有如下两个关键方法: loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象. findClass(String name):根据二进制名称来查找类. 如果需要实现自定义的ClassLoader,可以通过重写以上两

  • IntelliJ IDEA 中使用jRebel进行 Java 热部署教程图解

    jrebel JRebel是一套JavaEE开发工具.JRebel允许开发团队在有限的时间内完成更多的任务修正更多的问题,发布更高质量的软件产品. JRebel是收费软件,用户可以在JRebel官方站点下载30天的评估版本. Jrebel 可快速实现热部署,节省了大量重启时间,提高了个人开发效率. Rebel是一款JAVA虚拟机插件,它使得JAVA程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响.JRebel使你能即时分别看到代码.类和资源的变化,你可以一个个地上传而不

  • 深入学习Java 热部署的知识

    简介 在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作.对于某些大型的应用来说,每次的重启都需要花费大量的时间成本.虽然 osgi 架构的出现,让模块重启成为可能,但是如果模块之间有调用关系的话,这样的操作依然会让应用出现短暂的功能性休克.本文将探索如何在不破坏 Java 虚拟机现有行为的前提下,实现某个单一类的热部署,让系统无需重启就完成某个类的更新. 类加载的探索

  • Java语言中的自定义类加载器实例解析

    本文研究的主要是Java语言中的自定义类加载器实例解析的相关内容,具体如下. 自己写的类加载器 需要注意的是:如果想要对这个实例进行测试的话,首先需要在c盘建立一个c://myjava的目录.然后将相应的java文件放在这个目录中.并将产生的.clas文件放在c://myjava/com/lg.test目录下,否则是找不到的.这是要注意的.. class FileClassLoader : package com.lg.test; import java.io.ByteArrayOutputSt

  • JAVA集合框架工具类自定义Collections集合方法

    项目中有需要多次统计 某些集合中 的某个属性值,所以考虑封装一个方法,让其其定义实现计算方式. 话不多说,看代码: 1.封装的自定义集合工具类:CollectionsCustom package com.test.util; import java.util.Collection; import org.apache.commons.collections.CollectionUtils; /** * 自定义集合处理类 */ public class CollectionsCustom { /*

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

    热部署: 热部署就是在不重启应用的情况下,当类的定义即字节码文件修改后,能够替换该Class创建的对象.一般情况下,类的加载都是由系统自带的类加载器完成,且对于同一个全限定名的java类,只能被加载一次,而且无法被卸载.可以使用自定义的 ClassLoader 替换系统的加载器,创建一个新的 ClassLoader,再用它加载 Class,得到的 Class 对象就是新的(因为不是同一个类加载器),再用该 Class 对象创建一个实例,从而实现动态更新.如:修改 JSP 文件即生效,就是利用自定

  • Java通过自定义类加载器实现类隔离

    目录 前言 类隔离是什么 使用场景 解决方案 重写findClass 重写loadClass 总结 前言 由于微服务的快速迭代.持续集成等特性,越来越多的团队更倾向于它.但是也体现出了一些问题,比如在基础设施建设过程中,需要把通用功能下沉,把现有大而全的基础设施按领域拆分,考虑需要兼容现有生产服务,会产生不同的依赖版本,有时不注意就可以引发问题.比如本文遇到的依赖包版本冲突问题,以及如何利用类隔离技术解决的分析. 类隔离是什么 类隔离是一种通过类加载器实现加载所需类的实现方式,使得不同版本类间隔

  • SpringBoot实现devtools实现热部署过程解析

    1.修改pom.xml文件 <project> <dependencies> <!-- 使用devtool热部署插件(推荐) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional>

  • Intellij IDEA实现springboot热部署过程解析

    对于springboot热部署貌似是这样的,首先要设置idea相关配置 导航栏 File -> Settings -> Build,Execution,Deployment -> Compiler 选择Build project automatically 打勾 如下图所示 接着Ctrl+Shift+Alt+/ 快捷键选择Registry会弹出如下图 在红色选择的一行打勾,就完成了这步骤. 接着开始配置pom.xml文件 <dependencies> <dependen

  • Java基于zxing生成二维码矩阵过程解析

    这个例子需要使用google的开源项目zxing的核心jar包 core-3.2.0.jar 可以百度搜索下载jar文件,也可使用maven添加依赖 <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.2.0</version> </dependency> 下面是将生成的二维码矩阵写入

  • 详解Java的类加载机制及热部署的原理

    一.什么是类加载 类的加载指的是将类的.class文件的二进制数据读入到内存中,将其放在运行数据区的方法去,然后再堆区创建一个java.lang.Class对象,用来封装类在方法区的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区的数据结构,并且向Java程序员提供了访问方法区的数据结构的接口. 类加载器并不需要等到某个类被"首次主动使用"时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.cla

  • Java基础之自定义类加载器

    一.类加载器关系 自定义类加载器 创建一个类继承ClassLoader类,同时重写findClass方法,用于判断当前类的class文件是否已被加载 二.基于本地class文件的自定义类加载器 本地class文件路径 自定义类加载器: //创建自定义加载器类继承ClassLoader类 public class MyClassLoader extends ClassLoader{ // 包路径 private String Path; // 构造方法,用于初始化Path属性 public MyC

  • java 类加载与自定义类加载器详解

    类加载 所有类加载器,都是ClassLoader的子类. 类加载器永远以.class运行的目录为准. 读取classpath根目录下的文件有以下几种方式: 1 在Java项目中可以通过以下方式获取classspath下的文件: public void abc(){ //每一种读取方法,使用某个类获取Appclassloader ClassLoader cl = ReadFile.class.getClassLoader(); URL url = cl.getResource("a.txt&quo

  • java自定义类加载器代码示例

    如果要使用自定义类加载器加载class文件,就需要继承java.lang.ClassLoader类. ClassLoader有几个重要的方法: protectedClassLoader(ClassLoaderparent):使用指定的.用于委托操作的父类加载器创建新的类加载器. protectedfinalClass<?>defineClass(Stringname,byte[]b,intoff,intlen):将一个byte数组转换为Class类的实例. protectedClass<

随机推荐