文章目录
- 1. 常见的几种实现代码热更新的几种方式
- 对于开发环境我们可以使用
- 部署环境
- 1. 使用 Arthas 的 redefine 命令来加载新的 class 文件
- 2. 利用 URLClassLoader 动态加载
- 3. 通过Java的Instrumentation API 也是可以实现的
- 2. 实现
- 1. ClassScanner扫描目录和加载类
- 2. 定时任务定时加载指定路径下的类,使用上面的ClassScanner:
- 3. 在Spring配置中启用定时任务并添加ClassScanner:
1. 常见的几种实现代码热更新的几种方式
对于开发环境我们可以使用
- Spring Boot Devtools
- JRebel 插件
- IDEA的Debug 模式的热更新
部署环境
但是对于生产环境我们想要更新替换某个类 则无法使用上面几种方式,目前我知道的有如下几种
1. 使用 Arthas 的 redefine 命令来加载新的 class 文件
```bash
$ redefine /home/admin/User.class
```
其中,`/home/admin/User.class` 是你上传的新 class 文件的路径。
Arthas 会立即加载新的 class 文件,你的应用会立即使用新的代码逻辑。值得注意的是,
这种方式只适合修改方法内的代码逻辑,不适合增加方法或者修改方法签名,否则可能会引发 NoSuchMethodError
或 ClassFormatError
等错误。
这种方式只是临时更新 JVM 中的字节码,如果应用重启,修改的内容会丢失。因为这种方式可能带来一些风险,所以在生产环境中使用时需要谨慎。
2. 利用 URLClassLoader 动态加载
今天我们利用URLClassLoader 写一个简单的工具程序,内置到我们的应用中,方便我们在不停服务的情况下快速在发布环境验证我们的功能或者修复bug.
3. 通过Java的Instrumentation API 也是可以实现的
Instrumentation是Java语言中的一个API,它提供了一种在程序运行时监测、管理和修改Java字节码的能力。它允许开发者通过编程方式访问和操作类定义、方法和对象,从而实现各种动态的、非侵入式的操作。
这个不是我们今天的重点,我们今天通过URLClassLoader 和Spring 集成实现一个热加载工具。
2. 实现
1. ClassScanner扫描目录和加载类
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
public class ClassScanner {
private final String classPath;
private final ApplicationContext applicationContext;
public ClassScanner(String classPath, ApplicationContext applicationContext) {
this.classPath = classPath;
this.applicationContext = applicationContext;
}
public void scanAndLoad() throws Exception {
// 获取并遍历目录下的所有文件
File dir = new File(classPath);
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
// 只处理 .class 文件
if (file.isFile() && file.getName().endsWith(".class")) {
// 加载类
String className = file.getName().substring(0, file.getName().length() - 6);
URL[] urls = new URL[]{dir.toURI().toURL()};
try (URLClassLoader loader = new URLClassLoader(urls)) {
Class<?> clazz = loader.loadClass(className);
// 将类的实例添加到 Spring 容器
AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
beanFactory.autowireBean(clazz.newInstance());
}
}
}
}
}
}
2. 定时任务定时加载指定路径下的类,使用上面的ClassScanner:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScannerTask {
private final ClassScanner classScanner;
@Autowired
public ScannerTask(ClassScanner classScanner) {
this.classScanner = classScanner;
}
@Scheduled(fixedRate = 5000)
public void scan() throws Exception {
classScanner.scanAndLoad();
}
}
3. 在Spring配置中启用定时任务并添加ClassScanner:
每隔5秒,Spring就会执行一次ScannerTask.scan()方法,扫描目录并加载找到的类。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableScheduling;
import org.springframework.context.ApplicationContext;
@Configuration
@EnableScheduling
public class AppConfig {
@Autowired
private ApplicationContext applicationContext;
@Bean
public ClassScanner classScanner() {
return new ClassScanner("/tmp/classes", applicationContext);
}
}