简体   繁体   中英

Force enable spring-boot DevTools when running Jar

I am running my spring-boot app in a Docker container, trying to use remote LiveReload .

The spring-boot DevTools documentation states that

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”.

Is there any way to force the enabling of 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:


Understand what is going on

(1) LocalDevToolsAutoConfiguration configuration is conditional on @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. In my case, restarter.getInitialUrls() was returning 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(..)

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")

is only true when running from Eclipse (possibly any IDE? + springboot-maven-plugin?). To Recap:

  • isMain() returns false;

  • the initialUrls is not initialized;

  • the conditional LocalDevToolsAutoConfiguration is not configured;

  • no livereload.


A SOLUTION:

Make sure the classloader name is "AppClassLoader" by Creating your own AppClassLoader classloader. At the very first line of your spring-boot main, replace the classloader with yours:

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.

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.

make sure that devtools is included in the repackaged archive, like this:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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