簡體   English   中英

運行 Jar 時強制啟用 spring-boot DevTools

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

我在 Docker 容器中運行我的 spring-boot 應用程序,嘗試使用遠程 LiveReload

spring-boot DevTools 文檔指出

運行完全打包的應用程序時,開發人員工具會自動禁用。 如果您的應用程序是使用 java -jar 啟動的,或者是使用特殊的類加載器啟動的,那么它就被視為“生產應用程序”。

有沒有辦法強制啟用 DevTools?

解決方案是粗略的,因此您可以決定它是否適合您。 最終的解決方案是這篇文章的最后一部分

只是拋出解決方案很難,我首先需要解釋我是如何到達那里的。 首先,為什么在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.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")

僅當從 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不處理類路徑中更改的檢測。

確保 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