热部署:

热部署就是在不重启应用的情况下,当类的定义即字节码文件修改后,能够替换该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文件被修改过了
System.out.println("yes");
lastModified = now;
instance = createTask(); // 重新加载
}
}
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
});
t.setDaemon(true); // 守护进程
t.start();
}
} // 自定义的类加载器
class MyClassLoader extends ClassLoader {
private MyClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public Class<?> loadClass(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(null);
}
}

  

遇到的坑:

刚开始自定义类加载器时,重写的是 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(),这样就可以保证,目标类被自定义加载器加载,而java.lang.Runnable被BootStrap类加载器加载了。当然,如果被加载的类如果不在classpath下,就不会出现这些问题了。

还有就是路径问题:

  • 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使用自定义类加载器实现热部署的更多相关文章

  1. Java内存管理-掌握自定义类加载器的实现(七)

    勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇分析了ClassLoader的类加载相关的核心源码,也简单介绍了ClassLoa ...

  2. java自定义类加载器

    前言 java反射,最常用的Class.forName()方法.做毕设的时候,接收到代码字符串,通过 JavaCompiler将代码字符串生成A.class文件(存放在classpath下,也就是ec ...

  3. Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论

    Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...

  4. java类加载器学习2——自定义类加载器和父类委托机制带来的问题

    一.自定义类加载器的一般步骤 Java的类加载器自从JDK1.2开始便引入了一条机制叫做父类委托机制.一个类需要被加载的时候,JVM先会调用他的父类加载器进行加载,父类调用父类的父类,一直到顶级类加载 ...

  5. Java自定义类加载器与双亲委派模型

    其实,双亲委派模型并不复杂.自定义类加载器也不难!随便从网上搜一下就能搜出一大把结果,然后copy一下就能用.但是,如果每次想自定义类加载器就必须搜一遍别人的文章,然后复制,这样显然不行.可是自定义类 ...

  6. 【Java虚拟机8】自定义类加载器、类加载器命名空间、类的卸载

    前言 学习类加载器就一定要自己实现一个类加载器,今天就从一个简单的自定义类加载器说起. 自定义类加载器 例1 一个简单的类加载器,从一个给定的二进制名字读取一个字节码文件的内容,然后生成对应的clas ...

  7. [读书笔记]java中的类加载器

    以下内容大多来自周志明的<深入理解Java虚拟机>. 类加载器是java的一项创新,也是java流行的重要原因之一,它最初是为了满足java applet的需求而开发出来. 什么是appl ...

  8. JVM自定义类加载器加载指定classPath下的所有class及jar

    一.JVM中的类加载器类型 从Java虚拟机的角度讲,只有两种不同的类加载器:启动类加载器和其他类加载器. 1.启动类加载器(Boostrap ClassLoader):这个是由c++实现的,主要负责 ...

  9. jvm(1)类的加载(二)(自定义类加载器)

    [深入Java虚拟机]之四:类加载机制 1,从Java虚拟机的角度,只存在两种不同的类加载器: 1,启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有其他 ...

随机推荐

  1. Spring中常见的设计模式——代理模式

    一.代理模式的应用场景 生活中的中介,黄牛,等一系列帮助甲方做事的行为,都是代理模式的体现.代理模式(Proxy Pattern)是指为题对象提供一种代理,以控制对这个对象的访问.代理对象在客户端和目 ...

  2. 傅立叶变换—DFT

    背景:最近看到实验室其他同学在用傅立叶变换解决问题,我也想通过并行来解决这个问题,所以看了一下傅立叶变换的东西,感觉涵盖的东西还能多,我只是初步做了一下了解(一定很片面,但是我主要是为了应用它,主要了 ...

  3. 为什么大家都说Java中只有值传递?

    最近跟Java中的值传递和引用传递杠上了,一度怀疑人生.查了很多资料,加上自己的理解,终于搞清楚了,什么是值传递和引用传递.也搞明白了,为什么大家都说Java只有值传递,没有引用传递.原来,我一直以来 ...

  4. css控制div等比高度

    在移动端开发中,在banner轮播图未加载出来之前,banner层是不占文档流高度的,当从服务器获取完banner数据,展示的时候,banner层因为有了内容 所以会撑开,导致banner层下面的内容 ...

  5. C#实现DataTable转为Excel文件

    实现DataTable转为Excel文件,和上次分享的Excel文件转为DataTable互为反操作.DataTable转化为Excel文件是通过传入一个DataTable类型的参数,然后将传入的Da ...

  6. 【UEFI】---记录一次debug过程中的调试经验

    最近在调试一次SMBIOS的动态更新以及I2c设备的配置读取时,遇到了很多问题,特此总结: 1. 第一个是调试一个I2c设备的时候,遇到了一个很奇怪的问题,也由此问题总结了下SMBUS模块的知识,如下 ...

  7. transient简介

    当一个对象实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个的 ...

  8. [bzoj2286] [洛谷P2495] [sdoi2015] 消耗战

    Description 在一场战争中,战场由 \(n\) 个岛屿和 \(n-1\) 个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足 ...

  9. 机器学习-MNIST数据集使用二分类

    一.二分类训练MNIST数据集练习 %matplotlib inlineimport matplotlibimport numpy as npimport matplotlib.pyplot as p ...

  10. 生成链接中的全限定URL(Generating Fully Qualified URLs in Links) | 在视图中生成输出URL | 高级路由特性

    结果:<a class="myCSSClass"href="https://myserver.mydomain.com/Home/Index/MyId#myFrag ...