简体   繁体   English

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

[英]Force enable spring-boot DevTools when running Jar

I am running my spring-boot app in a Docker container, trying to use remote LiveReload .我在 Docker 容器中运行我的 spring-boot 应用程序,尝试使用远程 LiveReload

The spring-boot DevTools documentation states that spring-boot DevTools 文档指出

Developer tools are automatically disabled when running a fully packaged application.运行完全打包的应用程序时,开发人员工具会自动禁用。 If your application is launched using java -jar or if it's started using a special classloader, then it is considered a “production application”.如果您的应用程序是使用 java -jar 启动的,或者是使用特殊的类加载器启动的,那么它就被视为“生产应用程序”。

Is there any way to force the enabling of DevTools?有没有办法强制启用 DevTools?

The solution is sketchy, so you decide if it's good for you.解决方案是粗略的,因此您可以决定它是否适合您。 The final solution is the last part of this post最终的解决方案是这篇文章的最后一部分

It's hard to just throw the solution, I first need to explain how I got there.只是抛出解决方案很难,我首先需要解释我是如何到达那里的。 First, why livereload is not enabled when launching outside the IDE:首先,为什么在IDE外启动时没有启用livereload:


Understand what is going on了解正在发生的事情

(1) LocalDevToolsAutoConfiguration configuration is conditional on @ConditionalOnInitializedRestarter/OnInitializedRestarterCondition : (1) LocalDevToolsAutoConfiguration 配置以@ConditionalOnInitializedRestarter/OnInitializedRestarterCondition为条件:

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

(2) OnInitializedRestarterCondition retrieves a Restarter instance and checks if it's null otherwise restarter.getInitialUrls() returns null. (2) OnInitializedRestarterCondition 检索一个 Restarter 实例并检查它是否为 null,否则restarter.getInitialUrls()返回 null。 In my case, restarter.getInitialUrls() was returning 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) initialUrls is initialized in Restarter.class through DefaultRestartInitializer.getInitialUrls(..) (3) initialUrlsRestarter.class通过DefaultRestartInitializer.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") thread.getContextClassLoader() .getClass().getName().contains("AppClassLoader")

is only true when running from Eclipse (possibly any IDE? + springboot-maven-plugin?).仅当从 Eclipse(可能是任何 IDE?+ springboot-maven-plugin?)运行时才为真。 To Recap:回顾一下:

  • isMain() returns false; isMain() 返回 false;

  • the initialUrls is not initialized; initialUrls 未初始化;

  • the conditional LocalDevToolsAutoConfiguration is not configured;没有配置有条件的 LocalDevToolsAutoConfiguration;

  • no livereload.没有livereload。


A SOLUTION:一个办法:

Make sure the classloader name is "AppClassLoader" by Creating your own AppClassLoader classloader.通过创建您自己的 AppClassLoader 类加载器,确保类加载器名称为“AppClassLoader”。 At the very first line of your spring-boot main, replace the classloader with yours:在 spring-boot main 的第一行,用你的类加载器替换类加载器:

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

Our custom classloader implementation simply delegates to the original one:我们的自定义类加载器实现只是委托给原始的:

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();
}

} }

I configured CustomAppClassLoader as much as I could (calling super with 'url' and 'parent' from the original classloader), but anyway I still delegate all public methods to the original classloader.我尽可能多地配置了 CustomAppClassLoader(从原始类加载器中使用 'url' 和 'parent' 调用 super),但无论如何我仍然将所有公共方法委托给原始类加载器。

It works for me.这个对我有用。 Now, the better question is do I really want this :)现在,更好的问题是我真的想要这个 :)

A better option更好的选择

I think Spring Cloud's RestartEndpoint is a better option: Programmatically restart Spring Boot application However, RestartEndPoint does not handle the detection of changes in the classpath.我认为Spring Cloud's RestartEndpoint是一个更好的选择: Programmatically restart Spring Boot application但是, RestartEndPoint不处理类路径中更改的检测。

make sure that devtools is included in the repackaged archive, like this:确保 devtools 包含在重新打包的存档中,如下所示:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM