Java代码实现热部署
一.思路
0. 监听java文件最后修改时间,如果发生变化,则表示文件已经修改,进行重新编译
1. 编译java文件为 class文件
2. 通过手写类加载器,加载 class文件 ,创建对象
3. 反射创建对象 / 进行调用,(如果是web项目可以将创建的对象添加到spring容器中)
4. 调用测试
二.知识点
1. 自定义类加载器 继承 URLClassLoader 或 ClassLoader 都可以,继承 URLClassLoader 重写findClass(String name)方法即可实现加载class文件;
2. findClass方法核心语句 :return super.defineClass(String name, byte[] b, int off, int len)方法,b是class文件读取后得到的byte[]形式;
3. cmd窗口 使用javac即可将java文件编译成 class文件,在代码里使用 JavaCompiler 类,调用run方法即可编译指定java文件为class文件;
4. JavaCompiler不支持直接new,通过类ToolProvider.getSystemJavaCompiler()方法获取;
5. 通过类加载器获取的 class文件有时不方便调用,所以可以采用反射调用;
6. 对于一个java文件,可以通过File类的 lastModified获取最后修改时间,循环比较lastModified即可判断文件是否被修改;
7. class文件可以生成在任意目录,通过路径读取即可;
8. 选择合适的类加载器或自定义类加载器,对于电脑上任意位置的class文件完全都可以通过反射调用;
9. @SneakyThrows可以理解成 try-catch,使用需要导入lombok
10. 本demo是通过查阅资料和不断测试实现,如果有不足请指出;
三.实现
1. Demo概述
目标: 实现对HotTestService类的热部署,通过测试(main)监控java文件,如果java文件变动调用自定义类加载器MyClassLoader得到HotTestService的class对象,反射调用
2. 核心测试方法

package com.ahd.springtest.utils; import lombok.SneakyThrows; import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit; public class AhdgTest { //sourcePath 是java文件存放,编辑的路径
private static String sourcePath = "D:\\workspace_ms\\20210319\\springtest\\src\\main\\java";
//targetPath 是class文件存放路径
// private static String targetPath = "D:\\workspace_ms\\20210319\\springtest\\target\\classes";
private static String targetPath = "D:\\workspace_ms\\20210319\\springtest\\src\\main\\java";
private static String errPath = "D:\\文件清单\\hotlog.txt";//编译日志打印目录
private static String basePath = "\\com\\ahd\\springtest\\service\\HotTestService"; //包名 + 类名,路径形式 public static void main(String[] args) throws InterruptedException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, MalformedURLException, ClassNotFoundException {
//测试热部署
testHot();
}
/***
* 目标 : main方法循环调用,实时监测 com.ahd.springtest.service.HotTestService 是否发生改变,如果发生改变,重新加载并调用
*
* 0. 监听java文件最后修改时间,如果发生变化,则表示文件已经修改,进行重新编译
*
* 1. 编译java文件为 class文件
*
* 2. 通过手写类加载器加载 class文件 ,创建对象
*
* 3. 将新创建的对象 放入spring容器中
*
* 4. 调用测试
*
*/
public static void testHot() throws MalformedURLException, IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InterruptedException {
// 0. 监听java文件最后修改时间,(如果发生变化,则表示文件已经修改,需要进行重新编译)
File file = new File(sourcePath + basePath + ".java"); Thread thread = new Thread(new Runnable() {
@SneakyThrows //简化 try catch写法
@Override
public void run() {
Long lastModifiedTime = file.lastModified(); while (true) {
long timeEnd = file.lastModified();
if (timeEnd != lastModifiedTime) {
lastModifiedTime = timeEnd; // 1. 编译java文件为 class文件
try (InputStream is = new FileInputStream(file.getAbsolutePath());
OutputStream os = new FileOutputStream(targetPath + basePath + ".class");
OutputStream err = new FileOutputStream(errPath)) { JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
javaCompiler.run(is, os, err, sourcePath + basePath + ".java");//前三参数传入null 默认是 System.in,System.out.System.err } catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 2. 通过手写类加载器加载 class文件 ,创建对象
MyClassLoader instance = MyClassLoader.getInstance(new File(targetPath).toURI().toURL());
Class<?> aClass = instance.findClass("com.ahd.springtest.service.HotTestService"); Object o = aClass.newInstance();
// 3. 将新创建的对象 反射调用
Method test = aClass.getMethod("test");
Object invoke = test.invoke(o);
System.out.println(invoke);
}
try {
Thread.sleep(20);//检测频率:100ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.setDaemon(true);//设置成守护线程
thread.start(); //让主main一直运行,可以查看结果
while(true){
Thread.sleep(1000);
}
}
}
核心代码
3. 自定义类加载器MyClassLoader

package com.ahd.springtest.utils; import com.sun.xml.internal.ws.util.ByteArrayBuffer;
import lombok.SneakyThrows; import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader; public class MyClassLoader extends URLClassLoader {
private static MyClassLoader myClassLoader;//可以直接通过getInstance方法获取
private URL[] urls;
private URL url; public MyClassLoader(URL url) {
super(new URL[]{url});
this.url = url;
} public MyClassLoader(URL[] urls) {
super(urls);
this.urls = urls;
}
/***
* 1. name 是 类的 全限命名,通过全限名命 + 路径 获取 绝对路径
*
* 2. io获取字节码
*
* 3. 调用父类方法创建并返回class对象
* @param name
* @return
* @throws ClassNotFoundException
*/
@SneakyThrows //简化的 try catch写法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//1. name 是 类的 全限命名,通过全限名命 + 路径 获取 绝对路径
String path = url.getPath();
// System.out.println("yangdc log: " + path);
String classPath = path + name.replaceAll("\\.","/").concat(".class"); //2. io获取字节码
InputStream is = null;
URL url = null;
int b = 0;
ByteArrayBuffer bab = new ByteArrayBuffer();
try {
url = new URL("file:" + classPath);
// url = new URL("jar:" + classPath);
is = url.openStream();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
while((b = is.read())!=-1){
bab.write(b);
}
is.close(); //3. 调用父类方法创建并返回class对象
return super.defineClass(name,bab.toByteArray(),0,bab.size());
} public static MyClassLoader getInstance(URL url){
if (myClassLoader == null){
return new MyClassLoader(url);
}
return myClassLoader;
} public static MyClassLoader getInstance(URL[] url){
if (myClassLoader == null){
return new MyClassLoader(url);
}
return myClassLoader;
} public URL[] getUrls() {
return urls;
} public void setUrls(URL[] urls) {
this.urls = urls;
} public URL getUrl() {
return url;
} public void setUrl(URL url) {
this.url = url;
}
}
自定义类加载器
4. 被测试的类HotTestService
package com.ahd.springtest.service;
import org.springframework.stereotype.Service; @Service
public class HotTestService {
public HotTestService() {
} public String test() {
return "第39696633次测试hot";
}
}
l 测试结果来张图

Java代码实现热部署的更多相关文章
- 【nginx】nginx:利用负载均衡原理实现代码的热部署和灰度发布
事情起因很简单,代码的改动量很大.而且刚接手服务器,对原有的代码进行了一定程度的重构.虽然在测试服务器上做了较多的测试工作,但是直接将代码送入生产环境还是不放心,万一配置出问题服务直接崩溃怎么解?万一 ...
- SpringBoot2.0之七 实现页面和后台代码的热部署
开发过程中我可能经常会因为修改一点点代码就需要重启项目而烦恼,这样不仅很繁琐,还会因为不断重启浪费大量的时间,无法提高工作效率.可是现在SpringBoot为我们提供了非常简单的方式让我们实现热部署. ...
- IntelJ IDEA 进行Java Web开发+热部署+一些开发上的问题
基本上像放弃MyEclipse或者Eclipse了,因为IDEA现在也有对应的版本旗舰版和社区版了,而且使用更贴心,更给力,为什么还要选一个难用的要死的东西呢? 最近要开发一个Java Web项目,所 ...
- Java 调式、热部署、JVM 背后的支持者 Java Agent
我们平时写 Java Agent 的机会确实不多,也可以说几乎用不着.但其实我们一直在用它,而且接触的机会非常多.下面这些技术都使用了 Java Agent 技术,看一下你就知道为什么了. -各个 J ...
- 探秘 Java 热部署
# 前言 在之前的 深入浅出 JVM ClassLoader 一文中,我们说可以通过修改默认的类加载器实现热部署,但在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能 ...
- 使用spring-loaded开源项目,实现java程序和web应用的热部署
JDK1.5之后提供了java.lang.instrument.Instrumentation,即java agent机制可以实现类的redefinition和retransform. redefin ...
- Java代码自动部署
注:本文来源于<it小熊> [ ①Java代码自动部署-总结简介] 代码部署是每一个软件开发项目组都会有的一个流程,也是从开发环节到发布功能必不可少的环节.对于Java开发者来说,Java ...
- Spring Boot 五种热部署方式
[推荐]2019 Java 开发者跳槽指南.pdf(吐血整理)>>> 1.模板热部署 在SpringBoot中,模板引擎的页面默认是开启缓存的,如果修改了页面的内容,则刷新页面是得不 ...
- Spring Boot 五种热部署方式,极速开发就是生产力!
1.模板热部署 在 Spring Boot 中,模板引擎的页面默认是开启缓存的,如果修改了页面的内容,则刷新页面是得不到修改后的页面的,因此我们可以在application.properties中关闭 ...
随机推荐
- 如何实现 React 模块动态导入
如何实现 React 模块动态导入 React 模块动态导入 代码分割 webpack & code splitting https://reactjs.org/docs/code-split ...
- redux & multi dispatch & async await
redux & multi dispatch & async await 同时发送多个 action, 怎么保证按序返回数据 dispatch multi actions http:/ ...
- vue2.0用法以及环境配置
一.配置环境搭建 1.安装node.js (可以去官网看) 2.安装git (推荐看廖雪峰文章,点击查看) 3.安装vue: cmd:npm install vue //最新稳定版本 npm inst ...
- Kyle Tedford :人,一定要懂得转弯
每个人都渴望成功,但成功之路不仅仅只有一条. 有的时候,有一条路人满为患,每个人都挤破脑袋想要过去,然而能过去者,却寥寥无几.但有的人,却知道适时转弯,在新的道路上,摸索前进,最终通往成功. 最近,星 ...
- NGK公链:去中心化交易+挖矿生态体系共舞
NGK生态公链是一个安全.透明.专业的去中心化商业应用平台.作为一条具有技术信任甚至是公众信任的公链,NGK以区块链技术为支撑,利用区块链透明.公正.公开.数据不可篡改.分布式存储.可追溯等技术优势, ...
- 如何快速搞定websocket
5 个步骤快速掌握websocket消息发送和接收 1. 获取您的 appkey 先注册一个账号,登录后,创建一个应用,就能得到您的 appkey. 详情见 获取开发者账号和 appkey 2. 客户 ...
- 彻底理解c++的隐式类型转换
隐式类型转换可以说是我们的老朋友了,在代码里我们或多或少都会依赖c++的隐式类型转换. 然而不幸的是隐式类型转换也是c++的一大坑点,稍不注意很容易写出各种奇妙的bug. 因此我想借着本文来梳理一遍c ...
- 鸿蒙js开发7 鸿蒙分组列表和弹出menu菜单
鸿蒙入门指南,小白速来!从萌新到高手,怎样快速掌握鸿蒙开发?[课程入口]目录:1.鸿蒙视图效果2.js业务数据和事件3.页面视图代码4.跳转页面后的视图层5.js业务逻辑部分6.<鸿蒙js开发& ...
- 小白养成记——Linux中的用户和权限管理
1.用户组管理 每个用户都属于一个用户组,系统可以对一个用户组中的所有用户进行集中管理. 在创建用户时,如果未指定组,则系统会创建一个与用户名同名的组. 以下是关于用户组管理的一些基本命令: 新建用户 ...
- PHP Webshell List
目录 基础类 编码替换 无关键字函数类型 躲避检测记录 MySQL写入一句话 基础类 很容易被扫描.检测出来 <?php @eval($_GET['phpcode']);?> <?p ...