运行Jar时强制启用spring-boot DevTools

时间:2016-02-24 01:09:41

标签: java spring spring-boot

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

spring-boot DevTools documentation表示

  

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

有没有办法强制启用DevTools?

2 个答案:

答案 0 :(得分: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()。包含( “AppClassLoader”)

  

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

  • isMain()返回false;

  • initialUrls未初始化;

  • 未配置条件LocalDevToolsAutoConfiguration;

  • 没有实时重播。

解决方案:

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

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无法检测类路径中的更改。

答案 1 :(得分:0)

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

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