解决方案是粗略的,因此您决定它是否适合您。 最终解决方案是本文的最后部分
很难仅仅抛出解决方案,我首先需要解释我是如何到达那里的。一、为什么在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) initialUrls 在Restarter.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?)。回顾一下:
解决方案:
通过创建您自己的 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 不处理对类路径更改的检测。