【问题标题】:Force enable spring-boot DevTools when running Jar运行 Jar 时强制启用 spring-boot DevTools
【发布时间】:2016-06-06 02:37:45
【问题描述】:

我在 Docker 容器中运行我的 spring-boot 应用程序,尝试使用 remote LiveReload

spring-boot DevTools documentation 声明

运行完全打包的应用程序时,开发人员工具会自动禁用。如果您的应用程序是使用 java -jar 启动的,或者它是使用特殊的类加载器启动的,那么它被视为“生产应用程序”。

有没有办法强制启用 DevTools?

【问题讨论】:

    标签: java spring spring-boot


    【解决方案1】:

    确保 devtools 包含在重新打包的存档中,如下所示:

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludeDevtools>false</excludeDevtools>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    【讨论】:

      【解决方案2】:

      解决方案是粗略的,因此您决定它是否适合您。 最终解决方案是本文的最后部分

      很难仅仅抛出解决方案,我首先需要解释我是如何到达那里的。一、为什么在IDE外启动时没有启用livereload:


      了解发生了什么

      (1) LocalDevToolsAutoConfiguration 配置以@ConditionalOnInitializedRestarter/OnInitializedRestarterCondition为条件:

          @Configuration
          @ConditionalOnInitializedRestarter
          @EnableConfigurationProperties(DevToolsProperties.class)
          public class LocalDevToolsAutoConfiguration {
          ...
      

      (2) OnInitializedRestarterCondition 检索 Restarter 实例并检查它是否为 null,否则 restarter.getInitialUrls() 返回 null。就我而言,restarter.getInitialUrls() 返回 null。

      class OnInitializedRestarterCondition extends SpringBootCondition {
          @Override
          public ConditionOutcome getMatchOutcome(ConditionContext context,
                  AnnotatedTypeMetadata metadata) {
              Restarter restarter = getRestarter();
              if (restarter == null) {
                  return ConditionOutcome.noMatch("Restarter unavailable");
              }
              if (restarter.getInitialUrls() == null) {
                  return ConditionOutcome.noMatch("Restarter initialized without URLs");
              }
              return ConditionOutcome.match("Restarter available and initialized");
          }
      

      (3) initialUrlsRestarter.classDefaultRestartInitializer.getInitialUrls(..) 中初始化

      class Restarter{
          this.initialUrls = initializer.getInitialUrls(thread);
      }
      
      class DefaultRestartInitializer{
          @Override
          public URL[] getInitialUrls(Thread thread) {
              if (!isMain(thread)) {
                  return null;
              }
              for (StackTraceElement element : thread.getStackTrace()) {
                  if (isSkippedStackElement(element)) {
                      return null;
                  }
              }
              return getUrls(thread);
          }
      
          protected boolean isMain(Thread thread) {
          return thread.getName().equals("main") && thread.getContextClassLoader()
                  .getClass().getName().contains("AppClassLoader");
          }
      }
      

      thread.getContextClassLoader() .getClass().getName().contains("AppClassLoader")

      仅在从 Eclipse 运行时才成立(可能是任何 IDE?+ springboot-maven-plugin?)。回顾一下:

      • isMain() 返回 false;

      • initialUrls 未初始化;

      • 条件 LocalDevToolsAutoConfiguration 未配置;

      • 没有 livereload。


      解决方案:

      通过创建您自己的 AppClassLoader 类加载器,确保类加载器名称为“AppClassLoader”。 在 spring-boot main 的第一行,用你的替换类加载器:

      URLClassLoader originalClassLoader = (URLClassLoader)Thread.currentThread().getContextClassLoader();
      Thread.currentThread().setContextClassLoader(new CustomAppClassLoader(originalClassLoader));
      

      我们的自定义类加载器实现只是委托给原始类加载器:

      public class CustomAppClassLoader extends URLClassLoader{
      
      private URLClassLoader contextClassLoader;
      
      public CustomAppClassLoader(URLClassLoader contextClassLoader) {
          super(contextClassLoader.getURLs(), contextClassLoader.getParent());
          this.contextClassLoader = contextClassLoader;
      }
      
      public int hashCode() {
          return contextClassLoader.hashCode();
      }
      
      public boolean equals(Object obj) {
          return contextClassLoader.equals(obj);
      }
      
      public InputStream getResourceAsStream(String name) {
          return contextClassLoader.getResourceAsStream(name);
      }
      
      public String toString() {
          return contextClassLoader.toString();
      }
      
      public void close() throws IOException {
          contextClassLoader.close();
      }
      
      public URL[] getURLs() {
          return contextClassLoader.getURLs();
      }
      
      public Class<?> loadClass(String name) throws ClassNotFoundException {
          return contextClassLoader.loadClass(name);
      }
      
      public URL findResource(String name) {
          return contextClassLoader.findResource(name);
      }
      
      public Enumeration<URL> findResources(String name) throws IOException {
          return contextClassLoader.findResources(name);
      }
      
      public URL getResource(String name) {
          return contextClassLoader.getResource(name);
      }
      
      public Enumeration<URL> getResources(String name) throws IOException {
          return contextClassLoader.getResources(name);
      }
      
      public void setDefaultAssertionStatus(boolean enabled) {
          contextClassLoader.setDefaultAssertionStatus(enabled);
      }
      
      public void setPackageAssertionStatus(String packageName, boolean enabled) {
          contextClassLoader.setPackageAssertionStatus(packageName, enabled);
      }
      
      public void setClassAssertionStatus(String className, boolean enabled) {
          contextClassLoader.setClassAssertionStatus(className, enabled);
      }
      
      public void clearAssertionStatus() {
          contextClassLoader.clearAssertionStatus();
      }
      

      }

      我尽可能多地配置了 CustomAppClassLoader(使用原始类加载器中的 'url' 和 'parent' 调用 super),但无论如何我仍然将所有公共方法委托给原始类加载器。

      它对我有用。现在,更好的问题是我真的想要这个吗:)

      更好的选择

      我认为Spring Cloud's RestartEndpoint 是更好的选择:Programmatically restart Spring Boot application 但是,RestartEndPoint 不处理对类路径更改的检测。

      【讨论】:

        猜你喜欢
        • 2016-05-07
        • 2018-08-16
        • 1970-01-01
        • 2016-08-20
        • 2018-10-17
        • 2018-03-27
        • 2020-04-29
        • 1970-01-01
        • 2021-07-15
        相关资源
        最近更新 更多