spring-boot-devtools小记

Administrator
Administrator
发布于 2022-07-29 / 436 阅读
0
0

spring-boot-devtools小记

spring-boot-devtools是SpringBoot提供的开发期间的依赖项,可以提供热重载等实现。

但是使用过程也可能导致一些问题,主要是类加载器上的问题。

引入spring-boot-devtools后,工程被idea打开的部分下的所有java类启动时将采用RestartClassLoader(spring-boot-devtools提供)进行加载,而不被idea打开的其他module或者jar包,将不会采用RestartClassLoader加载,这会导致一些问题。

问题举例

工程A被idea打开,并依赖了spring-boot-devtools,工程A依赖的b.jar, 而b.jar中通过反射实例化了某个类C, 这个实例c中想通过如下方式获取一个SpringBean:

// C.java
package com.demo;
public class C {
	public final XXXService xxxService = SpringUtils.getBean(XXXService.class);
}


// SpringUtils.java
@Component
public class SpringUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext; // Spring应用上下文环境

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }
    
    public static <T> T getBean(Class<T> clz) throws BeansException {
        return applicationContext.getBean(clz);
    }
}

b.jar中是这么通过反射实例化C的:

C c = Class.forName("com.demo.C").newInstance();

上面这个案例中将会产生错误,SpringUtils.getBean(XXXService.class)将无法获取到XXXService的bean,通过debug, 可以发现,b.jar中反射实例化C时,使用的类加载器是AppClassLoader,这导致XXXService.class也是通过AppClassLoader加载的,而Spring BeanFactory中XXXService这个bean所在的类是通过RestartClassLoader加载的,这导致applicationContext.getBean(XXXService.class)无法正确获取到bean。具体源码位置大致截取如下:
image

type是AppClassLoader加载的, 而cache是一个map, key是Class, value是bean names, 由于xXXService这个bean对应的key的类加载器是RestartClassLoader,而这里的参数type的类加载器是AppClassLoader,因此,无法正确返回bean name。

解决办法

1. 移除spring-boot-devtools

如果不是很依赖这个,可以这么做。失去的就是热重载而已,即修改了部分代码,无需重新启动,会自动重新加载改了的文件。

2. 使用spring-devtools.properties指定哪些jar要用RestartClassLoader, 哪些不用

创建 resources/spring-devtools.properties:

restart.exclude.companycommonlibs=/mycorp-common-[\\w\\d-\\.]+\\.jar
restart.include.projectcommon=/mycorp-myproj-[\\w\\d-\\.]+\\.jar

上面include那项,就可以把 上面例子中的b.jar包含进去。那么b.jar也将使用RestartClassLoader加载,解决问题。

3. b.jar使用更兼容的方式实例化c

如:

C c = Thread.currentThread().getContextClassLoader().loadClass("com.demo.C").newInstance();

这样,程序启动时Thread.currentThread().getContextClassLoader()获取到的就是RestartClassLoader。解决问题。

使用 spring-boot-devtools

需要满足几个条件:

  1. pom引入
    <dependencies>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-devtools</artifactId>
              <optional>true</optional>
          </dependency>
     </dependencies>
    
  2. 配置文件开启(默认开启)
spring.devtools.restart.enabled=true
  1. idea设置支持
    image-1659055902589
    image-1659056515958

spring-boot-devtools和idea自带的热部署区别是?

IntelliJ IDEA 开发是否有必要用 spring-boot-devtools ?

参考


评论