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