一.思路

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";
}
}

测试结果来张图

Java代码实现热部署的更多相关文章

  1. 【nginx】nginx:利用负载均衡原理实现代码的热部署和灰度发布

    事情起因很简单,代码的改动量很大.而且刚接手服务器,对原有的代码进行了一定程度的重构.虽然在测试服务器上做了较多的测试工作,但是直接将代码送入生产环境还是不放心,万一配置出问题服务直接崩溃怎么解?万一 ...

  2. SpringBoot2.0之七 实现页面和后台代码的热部署

    开发过程中我可能经常会因为修改一点点代码就需要重启项目而烦恼,这样不仅很繁琐,还会因为不断重启浪费大量的时间,无法提高工作效率.可是现在SpringBoot为我们提供了非常简单的方式让我们实现热部署. ...

  3. IntelJ IDEA 进行Java Web开发+热部署+一些开发上的问题

    基本上像放弃MyEclipse或者Eclipse了,因为IDEA现在也有对应的版本旗舰版和社区版了,而且使用更贴心,更给力,为什么还要选一个难用的要死的东西呢? 最近要开发一个Java Web项目,所 ...

  4. Java 调式、热部署、JVM 背后的支持者 Java Agent

    我们平时写 Java Agent 的机会确实不多,也可以说几乎用不着.但其实我们一直在用它,而且接触的机会非常多.下面这些技术都使用了 Java Agent 技术,看一下你就知道为什么了. -各个 J ...

  5. 探秘 Java 热部署

    # 前言 在之前的 深入浅出 JVM ClassLoader 一文中,我们说可以通过修改默认的类加载器实现热部署,但在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能 ...

  6. 使用spring-loaded开源项目,实现java程序和web应用的热部署

    JDK1.5之后提供了java.lang.instrument.Instrumentation,即java agent机制可以实现类的redefinition和retransform. redefin ...

  7. Java代码自动部署

    注:本文来源于<it小熊> [ ①Java代码自动部署-总结简介] 代码部署是每一个软件开发项目组都会有的一个流程,也是从开发环节到发布功能必不可少的环节.对于Java开发者来说,Java ...

  8. Spring Boot 五种热部署方式

    [推荐]2019 Java 开发者跳槽指南.pdf(吐血整理)>>> 1.模板热部署 在SpringBoot中,模板引擎的页面默认是开启缓存的,如果修改了页面的内容,则刷新页面是得不 ...

  9. Spring Boot 五种热部署方式,极速开发就是生产力!

    1.模板热部署 在 Spring Boot 中,模板引擎的页面默认是开启缓存的,如果修改了页面的内容,则刷新页面是得不到修改后的页面的,因此我们可以在application.properties中关闭 ...

随机推荐

  1. GitHub Classroom

    GitHub Classroom GitHub Education https://classroom.github.com/classrooms https://classroom.github.c ...

  2. Github history viewer

    Github history viewer https://github.githistory.xyz/ https://github.com/pomber/git-history https://c ...

  3. flutter 让app跟随系统的theme

    首先你需要在"MaterialApp"设置两套theme MaterialApp( theme: myTheme, // light darkTheme: ThemeData.da ...

  4. 人物传记Kyle Tedford:数据环境生变,银行大数据风控怎么办?

    数据是金融业务的基石,监管集中清查大数据公司,很多东西在发生根本性改变,资金方做"甩手掌柜"的好日子不会重现.那些缺乏自主风控能力的金融机构,在未来的行业竞争中,恐无以立足了.近日 ...

  5. 初学c++,vc++6.0必备!

    文章首发 | 公众号:lunvey 作为一个纯粹的萌新,工作需要,刚接触到c++. 按照以往的经验,配置一个开发环境是首要的,其次便是边学边敲. c++入门书籍寻找了一堆,发现了一个共同点,在Wind ...

  6. SpringBoot+Vue豆宝社区前后端分离项目手把手实战系列教程01---搭建前端工程

    豆宝社区项目实战教程简介 本项目实战教程配有免费视频教程,配套代码完全开源.手把手从零开始搭建一个目前应用最广泛的Springboot+Vue前后端分离多用户社区项目.本项目难度适中,为便于大家学习, ...

  7. opencv打不开摄像头

    问题描述: capFace = cv2.VideoCapture(0) 报错: VIDEOIO ERROR: V4L2: Unable to capture video memory. VIDEOIO ...

  8. JDBC 用PreparedStatement语句动态操作SQL语句

    https://blog.csdn.net/u014453898/article/details/79038187 1.Statement 和 PreparedStatement: Statement ...

  9. ============================================ 微信小程序开发学习

    开发文档: https://developers.weixin.qq.com/miniprogram/dev/framework/

  10. 微信小程序:上滑触底加载下一页

    给商品列表页面添加一个上滑触底加载下一页的效果,滚动条触底之后就发送一个请求,来加载下一页数据, 先在getGoodsList中获取总条数 由于总页数需要再另外的一个方法中使用,所以要把总页数变成一个 ...