Spring Boot2.0 - 基于spring-boot-devtools实现热插拔

本文详细介绍如何在SpringBoot项目中配置并使用spring-boot-devtools模块实现代码修改后的自动重启,包括Maven配置、IDEA设置及热部署开关控制,适用于Java1.8+和SpringBoot2.1.5.RELEASE。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

一.你需要准备

  • Spring Boot 2.1.5.RELEASE
  • java 1.8+
  • Maven 3.3+
  • IDEA

二.开发人员工具介绍

Spring Boot包含一组额外的工具,可以使应用程序开发体验更加愉快。该spring-boot-devtools模块可以包含在任何项目中,以提供额外的开发时间功能。要包含devtools支持,请将模块依赖项添加到您的构建中,如如以下Maven和Gradle列表所示:

Maven.

<dependencies> 
	<dependency> 
		<groupId> org.springframework.boot </ groupId> 
		<artifactId> spring-boot-devtools </ artifactId> 
		<optional> true </ optional> 
	</ dependency> 
</ dependencies>

Gradle.

configurations {
	developmentOnly
	runtimeClasspath {
		extendsFrom developmentOnly
	}
}
dependencies {
	developmentOnly("org.springframework.boot:spring-boot-devtools")
}

三.配置热插拔

下面我们Maven为例,来实现自动部署新代码的功能:

3.1开启idea自动make功能

使用快捷键CTRL + SHIFT + A --> 查找 --> 选中

在这里插入图片描述

接下来,我们需要重启IDEA;

3.2 填加maven依赖

<!--热插拔-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<!--表示依赖不会传递-->
			<optional>true</optional>
		</dependency>

3.3 开启热部署

<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
				</configuration>
			</plugin>
		</plugins>
	</build>

3.4关闭热部署

配置完热部署后,默认自动开启,如果需要关闭热部署,可以参考如下方式进行关闭:

3.4.1方式一
@SpringBootApplication
public class MilogeniusWebApplication {

	public static void main(String[] args) {
		//关闭热部署
		System.setProperty("spring.devtools.restart.enabled","false");
		SpringApplication.run(MilogeniusWebApplication.class, args);
	}

}
3.4.2 方式二

在application.yml配置文件中增加如下配置:

spring:
  # 服务模块
  devtools:
    restart:
      # 热部署开关
      enabled: false

四.测试

首先我们启动项目,在尝试修改某个类之后,发现项目自动重启;将热部署开关关闭之后,发现修改某个类,项目不会自动重启;

在这里插入图片描述

五.总结

在Spring Boot 2.1.5.RELEASE版本测试下,我发现不配置true也能实现自动部署新代码,大家可以尝试一下,不知道是不是最新版本已经优化了;

返回专栏目录
<think>我们讨论的是在Spring Boot中重新加载已加载的类。这通常与热更新(Hot Reload)或热交换(Hot Swap)相关。在Java中,由于JVM的类加载机制,一旦一个类被加载,通常不能简单地被卸载然后重新加载(除非使用特殊的类加载器)。但是,在开发环境中,我们常常借助工具(如Spring Boot DevTools)来实现重新加载。在生产环境中,动态重新加载类则更为复杂,通常需要自定义类加载器。 根据引用[3],类加载分为两步:第一步是将class文件加载到内存中,第二步是将对应的bean注册到Spring容器。因此,重新加载一个类也需要考虑这两步:首先需要重新加载类定义(使用新的类加载器),然后重新注册相应的bean。 然而,标准的JVM不允许直接卸载一个类,除非卸载其类加载器。因此,常见的做法是: 1. 创建一个新的类加载器来加载修改后的类。 2. 卸载旧的类加载器(及其加载的所有类)。 3. 重新注册新的类到Spring容器。 但是,在Spring Boot中,我们可以利用一些内置机制或工具来实现开发时的热部署,以及生产环境中通过自定义机制实现。 下面分别从开发环境和生产环境来讨论: ### 一、开发环境:使用Spring Boot DevTools Spring Boot DevTools 提供了开发时的热部署功能,它通过监控类路径的变化,自动重启应用(但比冷启动更快,因为它使用了两个类加载器:一个用于基础类,一个用于开发类)。这样,当类发生改变时,应用会重新启动,从而实现重新加载。 配置方法: 1. 添加依赖(在pom.xml中): ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> ``` 2. 当修改类文件后,IDE会自动编译,Spring Boot DevTools会检测到变化并自动重启应用。 注意:DevTools的重启速度很快,因为它只重新加载开发类(用户代码),而基础类(如第三方库)由另一个类加载器加载,不会重新加载。 ### 二、生产环境:动态重新加载类 在生产环境中,通常不能频繁重启应用,因此需要更动态的机制。这可以通过以下步骤实现: 1. **自定义类加载器**:为每个需要重新加载的类(或模块)使用独立的类加载器。当需要重新加载时,丢弃旧的类加载器(从而卸载旧类),创建一个新的类加载器加载新的类字节码。 2. **重新注册Bean到Spring容器**:卸载旧的Bean(通过移除BeanDefinition),然后使用新的类创建BeanDefinition并注册。 具体步骤: **步骤1:加载新的类** 使用自定义的类加载器加载新的字节码(例如从外部jar文件或更新的class文件)。 ```java URLClassLoader newClassLoader = new URLClassLoader(new URL[]{new URL("file:path/to/updated-classes/")}, getClass().getClassLoader().getParent()); Class<?> reloadedClass = newClassLoader.loadClass("com.example.MyClass"); ``` **步骤2:卸载旧的Bean** 从Spring容器中移除旧的Bean定义(注意:需要确保没有其他Bean引用该Bean,否则可能会出错)。 ```java DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory(); beanFactory.removeBeanDefinition("myBean"); ``` **步骤3:注册新的Bean** 使用新的类创建BeanDefinition并注册。 ```java BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(reloadedClass); beanFactory.registerBeanDefinition("myBean", builder.getBeanDefinition()); ``` **步骤4:触发依赖注入** 如果该Bean被其他Bean依赖,可能需要刷新这些Bean(例如重新注入)。这很复杂,可能需要重新创建依赖它的Bean,或者刷新整个上下文。 注意:重新加载单个Bean可能会破坏应用状态,因为其他Bean可能持有对旧Bean的引用。因此,更安全的做法是重启整个应用,或者使用模块化设计(如OSGi)来支持热部署。 ### 三、使用JRebel等商业工具 JRebel是一个商业工具,它可以在运行时重新加载类,而无需重启应用。它通过修改JVM的行为来实现,但需要付费。 ### 四、注意事项 1. **内存泄漏**:频繁创建新的类加载器而不卸载旧的,会导致永久代(或元空间)内存泄漏,因为类加载器及其加载的类不会被回收,除非满足卸载条件(即没有任何活动引用)。 2. **状态丢失**:重新加载Bean后,旧Bean的状态会丢失,需要手动迁移状态(如果必要)。 3. **线程安全**:在重新加载过程中,应用可能还在处理请求,因此需要确保线程安全。 综上所述,在开发环境中,使用Spring Boot DevTools是最简单的方式。在生产环境中,动态重新加载类非常复杂,需要谨慎设计,通常建议使用模块化框架(如OSGi)或容器化技术(如Docker)来实现快速重启。 下面是一个简单的示例代码,展示如何动态重新加载一个Bean(生产环境需谨慎使用): ```java public void reloadBean(String beanName, String className, URL[] classpath) throws Exception { // 获取BeanFactory DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory(); // 移除旧Bean定义 if (beanFactory.containsBeanDefinition(beanName)) { beanFactory.removeBeanDefinition(beanName); } // 创建新的类加载器 URLClassLoader newClassLoader = new URLClassLoader(classpath, getClass().getClassLoader().getParent()); // 加载新类 Class<?> clazz = newClassLoader.loadClass(className); // 注册新的Bean定义 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz); beanFactory.registerBeanDefinition(beanName, builder.getBeanDefinition()); // 注意:这里没有处理依赖该Bean的其他Bean,可能需要刷新整个上下文 } ``` 但请注意,这种方法仅适用于重新加载单个Bean,且该Bean没有复杂的依赖关系。对于复杂的应用,建议使用更成熟的解决方案。 §§相关问题§§ 1. 如何在Spring Boot实现模块的热插拔2. 使用Spring Boot DevTools时,如何配置排除某些资源以避免重启? 3. 如何避免动态重新加载类导致的内存泄漏? 4. Spring Boot中如何实现零停机更新?</think>在 Spring Boot 中重新加载已加载的类(类热重载)需要结合类加载机制和 Spring 容器的动态管理能力。以下是实现方案及注意事项: --- ### 一、核心实现方案 #### 1. **自定义类加载器 + Bean 刷新** ```java // 1. 创建新的类加载器加载更新后的类 URLClassLoader newClassLoader = new URLClassLoader( new URL[]{new File("path/to/updated.jar").toURI().toURL()}, getClass().getClassLoader() ); // 2. 加载新类 Class<?> reloadedClass = newClassLoader.loadClass("com.example.UpdatedService"); // 3. 从容器中移除旧Bean DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); beanFactory.removeBeanDefinition("oldServiceBean"); // 4. 注册新Bean BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(reloadedClass); beanFactory.registerBeanDefinition("newServiceBean", builder.getBeanDefinition()); // 5. 刷新依赖该Bean的上下文(可选) context.getAutowireCapableBeanFactory().autowireBeanProperties( context.getBean("dependentBean"), AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true ); ``` #### 2. **使用 Spring Boot DevTools(开发环境)** ```xml <!-- pom.xml 配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> ``` - **工作原理**:通过双类加载器机制(BaseClassLoader 和 RestartClassLoader)实现快速重启[^2] - **触发条件**:修改类文件后自动触发重启(约1-2秒) - **限制**:仅适用于开发环境,生产环境不推荐 #### 3. **结合 JRebel(生产级方案)** - 商业工具 JRebel 实现真正的类热替换 - 无需重启应用,实时加载类变更 - 配置参考: ```bash java -agentpath:/path/to/jrebel/lib/libjrebel64.so -jar your-app.jar ``` --- ### 二、关键注意事项 1. **类加载器隔离** - 必须为更新类创建新的 `ClassLoader`(原类加载器无法卸载已加载类) - 卸载旧类加载器触发 GC:`newClassLoader.close()`(Java 7+)[^3] 2. **Bean 依赖管理** - 重新加载后需刷新依赖该 Bean 的其他组件(如上文步骤5) - 对 `@Autowired` 字段调用 `inject()` 重新注入 3. **作用域处理** ```java // 显式销毁 Singleton Bean context.getBean("oldServiceBean", DisposableBean.class).destroy(); ``` - 确保旧 Bean 实现 `DisposableBean` 或含 `@PreDestroy` 方法释放资源 4. **生产环境建议** - 使用模块化设计(OSGi 或 Java 9+ JPMS) - 结合容器化(Docker)实现滚动更新 - 避免频繁重载:内存泄漏风险(元空间溢出) --- ### 三、原理说明 1. **类加载机制** JVM 通过 `ClassLoader` 加载类,默认不允许替换已加载类(`ClassRedefinition` 需 Instrumentation API) ```mermaid graph LR A[原ClassLoader] --> B[类A v1] C[新ClassLoader] --> D[类A v2] ``` -版本类需由新类加载器加载[^3] 2. **Spring 容器协作** - 移除 `BeanDefinition` 销毁旧实例 - 注册新 `BeanDefinition` 触发依赖解析 - 需手动处理非 Spring 管理的引用 --- ### 四、备选方案对比 | 方案 | 适用场景 | 重启时间 | 生产可用 | |---------------------|---------------|----------|----------| | Spring DevTools | 开发环境 | 1-2秒 | ❌ | | 自定义类加载器 | 生产热插件 | 毫秒级 | ✅ | | JRebel | 全环境 | 0 | ✅ | | 容器化重启 | 生产环境 | 10-30秒 | ✅ | --- ### 五、常见问题解决 **Q: 重载后出现 `ClassCastException`?** A: 确保新旧类由**不同类加载器**加载,且调用方使用新加载器实例: ```java // 错误:旧加载器类 vs 新加载器实例 OldInterface obj = (OldInterface) newClassLoader.loadClass(...).newInstance(); // 正确:通过接口隔离 public interface ReloadableModule {} // 公共接口由父加载器加载 ``` **Q: 元空间(Metaspace)持续增长?** A: 添加 JVM 参数限制并监控: ```bash -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError ``` **Q: 如何监控类重载状态?** A: 注册监听器: ```java context.addApplicationListener(new ApplicationListener<ContextRefreshedEvent>() { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println("Bean刷新完成"); } }); ``` ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值