Force spring-boot DevTools on Jar

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

Spring-boot DevTools documentation claims that

Developer tools are automatically disabled when starting a fully packaged application. If your application is launched using java -jar or if it is launched using a special class loader, then it is considered a "production application".

Is there a way to force enable DevTools?

+3
source share
1 answer

The decision is sketchy, so you decide whether it is good for you. The final decision is the last part of this post.

It’s hard to just throw a decision, I first need to explain how I got there. First, why when you start outside the IDE, the enable function is not enabled:


Understand what's going on

(1) LocalDevToolsAutoConfiguration configuration depends on @ConditionalOnInitializedRestarter/OnInitializedRestarterCondition :

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

(2) OnInitializedRestarterCondition returns a Restarter instance and checks if it is null otherwise restarter.getInitialUrls() returns null. In my case, restarter.getInitialUrls() returned 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 initialized in Restarter.class via 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")

valid only when starting from Eclipse (possibly any IDE? + springboot-maven-plugin?). Update:

  • isMain () returns false;

  • initialUrls is not initialized;

  • The LocalDevToolsAutoConfiguration conditional function is not configured;

  • no load on the liver.


DECISION:

Make sure that the class loader name is "AppClassLoader" by creating your own class loader, AppClassLoader. On the very first line of your spring-boot main, replace the class loader with the following:

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

Our custom implementation of the class loader simply delegates it to the original:

 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 source class loader), but in any case, I still delegated all the public methods to the source class loader.

This works for me. Now, the best question is, I really want it :)

The best way

I think Spring Cloud RestartEndpoint is the best option: Programmatically restart the Spring application to load. However, RestartEndPoint does not handle detection of changes to the class path.

+2
source

All Articles