Don't quit application on restart failures
Update `Restarter` to support a `FailureHandler` strategy that can be used to determine how to deal with errors. The `LocalDevToolsAutoConfiguration` now uses a strategy that doesn't quit the application, but instead continues to wait for further file changes. This helps make restart much more usable in situations where you accidentally break code. Fixes gh-3210
This commit is contained in:
parent
24fc94461b
commit
099db11754
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.devtools.autoconfigure;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.springframework.boot.devtools.classpath.ClassPathFolders;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFiles;
|
||||
import org.springframework.boot.devtools.filewatch.FileChangeListener;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcherFactory;
|
||||
import org.springframework.boot.devtools.restart.FailureHandler;
|
||||
import org.springframework.boot.devtools.restart.Restarter;
|
||||
|
||||
/**
|
||||
* {@link FailureHandler} that waits for filesystem changes before retrying.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class FileWatchingFailureHandler implements FailureHandler {
|
||||
|
||||
private final FileSystemWatcherFactory fileSystemWatcherFactory;
|
||||
|
||||
public FileWatchingFailureHandler(FileSystemWatcherFactory fileSystemWatcherFactory) {
|
||||
this.fileSystemWatcherFactory = fileSystemWatcherFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Outcome handle(Throwable failure) {
|
||||
failure.printStackTrace();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
FileSystemWatcher watcher = this.fileSystemWatcherFactory.getFileSystemWatcher();
|
||||
watcher.addSourceFolders(new ClassPathFolders(Restarter.getInstance()
|
||||
.getInitialUrls()));
|
||||
watcher.addListener(new Listener(latch));
|
||||
watcher.start();
|
||||
try {
|
||||
latch.await();
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
}
|
||||
return Outcome.RETRY;
|
||||
}
|
||||
|
||||
private static class Listener implements FileChangeListener {
|
||||
|
||||
private final CountDownLatch latch;
|
||||
|
||||
public Listener(CountDownLatch latch) {
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(Set<ChangedFiles> changeSet) {
|
||||
System.out.println("Changes");
|
||||
this.latch.countDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher;
|
|||
import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy;
|
||||
import org.springframework.boot.devtools.classpath.PatternClassPathRestartStrategy;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcherFactory;
|
||||
import org.springframework.boot.devtools.livereload.LiveReloadServer;
|
||||
import org.springframework.boot.devtools.restart.ConditionalOnInitializedRestarter;
|
||||
import org.springframework.boot.devtools.restart.RestartScope;
|
||||
|
|
@ -114,8 +115,8 @@ public class LocalDevToolsAutoConfiguration {
|
|||
@EventListener
|
||||
public void onClassPathChanged(ClassPathChangedEvent event) {
|
||||
if (event.isRestartRequired()) {
|
||||
getFileSystemWatcher().stop();
|
||||
Restarter.getInstance().restart();
|
||||
Restarter.getInstance().restart(
|
||||
new FileWatchingFailureHandler(getFileSystemWatcherFactory()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,8 +124,10 @@ public class LocalDevToolsAutoConfiguration {
|
|||
@ConditionalOnMissingBean
|
||||
public ClassPathFileSystemWatcher classPathFileSystemWatcher() {
|
||||
URL[] urls = Restarter.getInstance().getInitialUrls();
|
||||
return new ClassPathFileSystemWatcher(getFileSystemWatcher(),
|
||||
classPathRestartStrategy(), urls);
|
||||
ClassPathFileSystemWatcher watcher = new ClassPathFileSystemWatcher(
|
||||
getFileSystemWatcherFactory(), classPathRestartStrategy(), urls);
|
||||
watcher.setStopWatcherOnRestart(true);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
@ -135,7 +138,18 @@ public class LocalDevToolsAutoConfiguration {
|
|||
}
|
||||
|
||||
@Bean
|
||||
public FileSystemWatcherFactory getFileSystemWatcherFactory() {
|
||||
return new FileSystemWatcherFactory() {
|
||||
|
||||
@Override
|
||||
public FileSystemWatcher getFileSystemWatcher() {
|
||||
return newFileSystemWatcher();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private FileSystemWatcher newFileSystemWatcher() {
|
||||
Restart restartProperties = this.properties.getRestart();
|
||||
FileSystemWatcher watcher = new FileSystemWatcher(true,
|
||||
restartProperties.getPollInterval(),
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import java.util.Set;
|
|||
import org.springframework.boot.devtools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFiles;
|
||||
import org.springframework.boot.devtools.filewatch.FileChangeListener;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
|
@ -30,33 +30,44 @@ import org.springframework.util.Assert;
|
|||
* ClassPathChangedEvents}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
* @see ClassPathFileSystemWatcher
|
||||
*/
|
||||
public class ClassPathFileChangeListener implements FileChangeListener {
|
||||
class ClassPathFileChangeListener implements FileChangeListener {
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
private final ClassPathRestartStrategy restartStrategy;
|
||||
|
||||
private final FileSystemWatcher fileSystemWatcherToStop;
|
||||
|
||||
/**
|
||||
* Create a new {@link ClassPathFileChangeListener} instance.
|
||||
* @param eventPublisher the event publisher used send events
|
||||
* @param restartStrategy the restart strategy to use
|
||||
* @param fileSystemWatcherToStop the file system watcher to stop on a restart (or
|
||||
* {@code null})
|
||||
*/
|
||||
public ClassPathFileChangeListener(ApplicationEventPublisher eventPublisher,
|
||||
ClassPathRestartStrategy restartStrategy) {
|
||||
ClassPathRestartStrategy restartStrategy,
|
||||
FileSystemWatcher fileSystemWatcherToStop) {
|
||||
Assert.notNull(eventPublisher, "EventPublisher must not be null");
|
||||
Assert.notNull(restartStrategy, "RestartStrategy must not be null");
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.restartStrategy = restartStrategy;
|
||||
this.fileSystemWatcherToStop = fileSystemWatcherToStop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(Set<ChangedFiles> changeSet) {
|
||||
boolean restart = isRestartRequired(changeSet);
|
||||
ApplicationEvent event = new ClassPathChangedEvent(this, changeSet, restart);
|
||||
publishEvent(new ClassPathChangedEvent(this, changeSet, restart));
|
||||
}
|
||||
|
||||
private void publishEvent(ClassPathChangedEvent event) {
|
||||
this.eventPublisher.publishEvent(event);
|
||||
if (event.isRestartRequired() && this.fileSystemWatcherToStop != null) {
|
||||
this.fileSystemWatcherToStop.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRestartRequired(Set<ChangedFiles> changeSet) {
|
||||
|
|
|
|||
|
|
@ -18,16 +18,14 @@ package org.springframework.boot.devtools.classpath;
|
|||
|
||||
import java.net.URL;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcherFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Encapsulates a {@link FileSystemWatcher} to watch the local classpath folders for
|
||||
|
|
@ -40,63 +38,37 @@ import org.springframework.util.ResourceUtils;
|
|||
public class ClassPathFileSystemWatcher implements InitializingBean, DisposableBean,
|
||||
ApplicationContextAware {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ClassPathFileSystemWatcher.class);
|
||||
|
||||
private final FileSystemWatcher fileSystemWatcher;
|
||||
|
||||
private ClassPathRestartStrategy restartStrategy;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* Create a new {@link ClassPathFileSystemWatcher} instance.
|
||||
* @param urls the classpath URLs to watch
|
||||
*/
|
||||
public ClassPathFileSystemWatcher(URL[] urls) {
|
||||
this(new FileSystemWatcher(), null, urls);
|
||||
}
|
||||
private boolean stopWatcherOnRestart;
|
||||
|
||||
/**
|
||||
* Create a new {@link ClassPathFileSystemWatcher} instance.
|
||||
* @param fileSystemWatcherFactory the underlying {@link FileSystemWatcher} used to
|
||||
* monitor the local file system
|
||||
* @param restartStrategy the classpath restart strategy
|
||||
* @param urls the URLs to watch
|
||||
*/
|
||||
public ClassPathFileSystemWatcher(ClassPathRestartStrategy restartStrategy, URL[] urls) {
|
||||
this(new FileSystemWatcher(), restartStrategy, urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ClassPathFileSystemWatcher} instance.
|
||||
* @param fileSystemWatcher the underlying {@link FileSystemWatcher} used to monitor
|
||||
* the local file system
|
||||
* @param restartStrategy the classpath restart strategy
|
||||
* @param urls the URLs to watch
|
||||
*/
|
||||
public ClassPathFileSystemWatcher(FileSystemWatcher fileSystemWatcher,
|
||||
public ClassPathFileSystemWatcher(FileSystemWatcherFactory fileSystemWatcherFactory,
|
||||
ClassPathRestartStrategy restartStrategy, URL[] urls) {
|
||||
Assert.notNull(fileSystemWatcher, "FileSystemWatcher must not be null");
|
||||
Assert.notNull(fileSystemWatcherFactory,
|
||||
"FileSystemWatcherFactory must not be null");
|
||||
Assert.notNull(urls, "Urls must not be null");
|
||||
this.fileSystemWatcher = fileSystemWatcher;
|
||||
this.fileSystemWatcher = fileSystemWatcherFactory.getFileSystemWatcher();
|
||||
this.restartStrategy = restartStrategy;
|
||||
addUrls(urls);
|
||||
this.fileSystemWatcher.addSourceFolders(new ClassPathFolders(urls));
|
||||
}
|
||||
|
||||
private void addUrls(URL[] urls) {
|
||||
for (URL url : urls) {
|
||||
addUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
private void addUrl(URL url) {
|
||||
if (url.getProtocol().equals("file") && url.getPath().endsWith("/")) {
|
||||
try {
|
||||
this.fileSystemWatcher.addSourceFolder(ResourceUtils.getFile(url));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.warn("Unable to watch classpath URL " + url);
|
||||
logger.trace("Unable to watch classpath URL " + url, ex);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set if the {@link FileSystemWatcher} should be stopped when a full restart occurs.
|
||||
* @param stopWatcherOnRestart if the watcher should be stopped when a restart occurs
|
||||
*/
|
||||
public void setStopWatcherOnRestart(boolean stopWatcherOnRestart) {
|
||||
this.stopWatcherOnRestart = stopWatcherOnRestart;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -108,8 +80,12 @@ public class ClassPathFileSystemWatcher implements InitializingBean, DisposableB
|
|||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (this.restartStrategy != null) {
|
||||
FileSystemWatcher watcherToStop = null;
|
||||
if (this.stopWatcherOnRestart) {
|
||||
watcherToStop = this.fileSystemWatcher;
|
||||
}
|
||||
this.fileSystemWatcher.addListener(new ClassPathFileChangeListener(
|
||||
this.applicationContext, this.restartStrategy));
|
||||
this.applicationContext, this.restartStrategy, watcherToStop));
|
||||
}
|
||||
this.fileSystemWatcher.start();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.devtools.classpath;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Provides access to entries on the classpath that refer to folders.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class ClassPathFolders implements Iterable<File> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ClassPathFolders.class);
|
||||
|
||||
private final List<File> folders = new ArrayList<File>();
|
||||
|
||||
public ClassPathFolders(URL[] urls) {
|
||||
if (urls != null) {
|
||||
addUrls(urls);
|
||||
}
|
||||
}
|
||||
|
||||
private void addUrls(URL[] urls) {
|
||||
for (URL url : urls) {
|
||||
addUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
private void addUrl(URL url) {
|
||||
if (url.getProtocol().equals("file") && url.getPath().endsWith("/")) {
|
||||
try {
|
||||
this.folders.add(ResourceUtils.getFile(url));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.warn("Unable to get classpath URL " + url);
|
||||
logger.trace("Unable to get classpath URL " + url, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<File> iterator() {
|
||||
return Collections.unmodifiableList(this.folders).iterator();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -95,6 +95,18 @@ public class FileSystemWatcher {
|
|||
this.listeners.add(fileChangeListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a source folders to monitor. Cannot be called after the watcher has been
|
||||
* {@link #start() started}.
|
||||
* @param folders the folders to monitor
|
||||
*/
|
||||
public void addSourceFolders(Iterable<File> folders) {
|
||||
Assert.notNull(folders, "Folders must not be null");
|
||||
for (File folder : folders) {
|
||||
addSourceFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a source folder to monitor. Cannot be called after the watcher has been
|
||||
* {@link #start() started}.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.devtools.filewatch;
|
||||
|
||||
/**
|
||||
* Factory used to create new {@link FileSystemWatcher} instances.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public interface FileSystemWatcherFactory {
|
||||
|
||||
/**
|
||||
* Create a new {@link FileSystemWatcher}.
|
||||
* @return a new {@link FileSystemWatcher}
|
||||
*/
|
||||
FileSystemWatcher getFileSystemWatcher();
|
||||
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@ import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher;
|
|||
import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy;
|
||||
import org.springframework.boot.devtools.classpath.PatternClassPathRestartStrategy;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcherFactory;
|
||||
import org.springframework.boot.devtools.livereload.LiveReloadServer;
|
||||
import org.springframework.boot.devtools.restart.DefaultRestartInitializer;
|
||||
import org.springframework.boot.devtools.restart.RestartScope;
|
||||
|
|
@ -188,12 +189,23 @@ public class RemoteClientConfiguration {
|
|||
if (urls == null) {
|
||||
urls = new URL[0];
|
||||
}
|
||||
return new ClassPathFileSystemWatcher(getFileSystemWather(),
|
||||
return new ClassPathFileSystemWatcher(getFileSystemWatcherFactory(),
|
||||
classPathRestartStrategy(), urls);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FileSystemWatcher getFileSystemWather() {
|
||||
public FileSystemWatcherFactory getFileSystemWatcherFactory() {
|
||||
return new FileSystemWatcherFactory() {
|
||||
|
||||
@Override
|
||||
public FileSystemWatcher getFileSystemWatcher() {
|
||||
return newFileSystemWatcher();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private FileSystemWatcher newFileSystemWatcher() {
|
||||
Restart restartProperties = this.properties.getRestart();
|
||||
FileSystemWatcher watcher = new FileSystemWatcher(true,
|
||||
restartProperties.getPollInterval(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.devtools.restart;
|
||||
|
||||
/**
|
||||
* Strategy used to handle launch failures.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public interface FailureHandler {
|
||||
|
||||
/**
|
||||
* {@link FailureHandler} that always aborts.
|
||||
*/
|
||||
public static final FailureHandler NONE = new FailureHandler() {
|
||||
|
||||
@Override
|
||||
public Outcome handle(Throwable failure) {
|
||||
return Outcome.ABORT;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a run failure. Implementations may block, for example to wait until specific
|
||||
* files are updated.
|
||||
* @param failure the exception
|
||||
* @return the outcome
|
||||
*/
|
||||
Outcome handle(Throwable failure);
|
||||
|
||||
/**
|
||||
* Various outcomes for the handler.
|
||||
*/
|
||||
public static enum Outcome {
|
||||
|
||||
/**
|
||||
* Abort the relaunch.
|
||||
*/
|
||||
ABORT,
|
||||
|
||||
/**
|
||||
* Try again to relaunch the application.
|
||||
*/
|
||||
RETRY
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -29,6 +29,8 @@ class RestartLauncher extends Thread {
|
|||
|
||||
private final String[] args;
|
||||
|
||||
private Throwable error;
|
||||
|
||||
public RestartLauncher(ClassLoader classLoader, String mainClassName, String[] args,
|
||||
UncaughtExceptionHandler exceptionHandler) {
|
||||
this.mainClassName = mainClassName;
|
||||
|
|
@ -46,9 +48,13 @@ class RestartLauncher extends Thread {
|
|||
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
|
||||
mainMethod.invoke(null, new Object[] { this.args });
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
catch (Throwable ex) {
|
||||
this.error = ex;
|
||||
}
|
||||
}
|
||||
|
||||
public Throwable getError() {
|
||||
return this.error;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import org.apache.commons.logging.LogFactory;
|
|||
import org.springframework.beans.CachedIntrospectionResults;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.devtools.restart.FailureHandler.Outcome;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
|
||||
import org.springframework.boot.devtools.restart.classloader.RestartClassLoader;
|
||||
import org.springframework.boot.logging.DeferredLog;
|
||||
|
|
@ -160,7 +161,7 @@ public class Restarter {
|
|||
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
start();
|
||||
start(FailureHandler.NONE);
|
||||
cleanupCaches();
|
||||
return null;
|
||||
}
|
||||
|
|
@ -227,6 +228,14 @@ public class Restarter {
|
|||
* Restart the running application.
|
||||
*/
|
||||
public void restart() {
|
||||
restart(FailureHandler.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the running application.
|
||||
* @param failureHandler a failure handler to deal with application that don't start
|
||||
*/
|
||||
public void restart(final FailureHandler failureHandler) {
|
||||
if (!this.enabled) {
|
||||
this.logger.debug("Application restart is disabled");
|
||||
return;
|
||||
|
|
@ -237,7 +246,7 @@ public class Restarter {
|
|||
@Override
|
||||
public Void call() throws Exception {
|
||||
Restarter.this.stop();
|
||||
Restarter.this.start();
|
||||
Restarter.this.start(failureHandler);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -246,9 +255,26 @@ public class Restarter {
|
|||
|
||||
/**
|
||||
* Start the application.
|
||||
* @param failureHandler a failure handler for application that wont start
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void start() throws Exception {
|
||||
protected void start(FailureHandler failureHandler) throws Exception {
|
||||
do {
|
||||
Throwable error = doStart();
|
||||
if (error == null) {
|
||||
return;
|
||||
}
|
||||
if (failureHandler.handle(error) == Outcome.ABORT) {
|
||||
if (error instanceof Exception) {
|
||||
throw (Exception) error;
|
||||
}
|
||||
throw new Exception(error);
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
private Throwable doStart() throws Exception {
|
||||
Assert.notNull(this.mainClassName, "Unable to find the main class to restart");
|
||||
ClassLoader parent = this.applicationClassLoader;
|
||||
URL[] urls = this.urls.toArray(new URL[this.urls.size()]);
|
||||
|
|
@ -259,19 +285,21 @@ public class Restarter {
|
|||
this.logger.debug("Starting application " + this.mainClassName
|
||||
+ " with URLs " + Arrays.asList(urls));
|
||||
}
|
||||
relaunch(classLoader);
|
||||
return relaunch(classLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relaunch the application using the specified classloader.
|
||||
* @param classLoader the classloader to use
|
||||
* @return any exception that caused the launch to fail or {@code null}
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void relaunch(ClassLoader classLoader) throws Exception {
|
||||
protected Throwable relaunch(ClassLoader classLoader) throws Exception {
|
||||
RestartLauncher launcher = new RestartLauncher(classLoader, this.mainClassName,
|
||||
this.args, this.exceptionHandler);
|
||||
launcher.start();
|
||||
launcher.join();
|
||||
return launcher.getError();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfigurati
|
|||
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFiles;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
|
||||
import org.springframework.boot.devtools.livereload.LiveReloadServer;
|
||||
import org.springframework.boot.devtools.restart.FailureHandler;
|
||||
import org.springframework.boot.devtools.restart.MockRestartInitializer;
|
||||
import org.springframework.boot.devtools.restart.MockRestarter;
|
||||
import org.springframework.boot.devtools.restart.Restarter;
|
||||
|
|
@ -48,6 +48,7 @@ import static org.hamcrest.Matchers.equalTo;
|
|||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
|
|
@ -138,7 +139,7 @@ public class LocalDevToolsAutoConfigurationTests {
|
|||
ClassPathChangedEvent event = new ClassPathChangedEvent(this.context,
|
||||
Collections.<ChangedFiles> emptySet(), true);
|
||||
this.context.publishEvent(event);
|
||||
verify(this.mockRestarter.getMock()).restart();
|
||||
verify(this.mockRestarter.getMock()).restart(any(FailureHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -172,7 +173,10 @@ public class LocalDevToolsAutoConfigurationTests {
|
|||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
properties.put("spring.devtools.restart.trigger-file", "somefile.txt");
|
||||
this.context = initializeAndRun(Config.class, properties);
|
||||
FileSystemWatcher watcher = this.context.getBean(FileSystemWatcher.class);
|
||||
ClassPathFileSystemWatcher classPathWatcher = this.context
|
||||
.getBean(ClassPathFileSystemWatcher.class);
|
||||
Object watcher = ReflectionTestUtils.getField(classPathWatcher,
|
||||
"fileSystemWatcher");
|
||||
Object filter = ReflectionTestUtils.getField(watcher, "triggerFilter");
|
||||
assertThat(filter, instanceOf(TriggerFileFilter.class));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,19 +27,18 @@ import org.junit.Test;
|
|||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathFileChangeListener;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFiles;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
|
|
@ -52,6 +51,15 @@ public class ClassPathFileChangeListenerTests {
|
|||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Mock
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@Mock
|
||||
private ClassPathRestartStrategy restartStrategy;
|
||||
|
||||
@Mock
|
||||
private FileSystemWatcher fileSystemWatcher;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<ApplicationEvent> eventCaptor;
|
||||
|
||||
|
|
@ -64,31 +72,32 @@ public class ClassPathFileChangeListenerTests {
|
|||
public void eventPublisherMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("EventPublisher must not be null");
|
||||
new ClassPathFileChangeListener(null, mock(ClassPathRestartStrategy.class));
|
||||
new ClassPathFileChangeListener(null, this.restartStrategy,
|
||||
this.fileSystemWatcher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restartStrategyMustNotBeNull() throws Exception {
|
||||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("RestartStrategy must not be null");
|
||||
new ClassPathFileChangeListener(mock(ApplicationEventPublisher.class), null);
|
||||
new ClassPathFileChangeListener(this.eventPublisher, null, this.fileSystemWatcher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendsEventWithoutRestart() throws Exception {
|
||||
testSendsEvent(false);
|
||||
verify(this.fileSystemWatcher, never()).stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendsEventWithRestart() throws Exception {
|
||||
testSendsEvent(true);
|
||||
verify(this.fileSystemWatcher).stop();
|
||||
}
|
||||
|
||||
private void testSendsEvent(boolean restart) {
|
||||
ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class);
|
||||
ClassPathRestartStrategy restartStrategy = mock(ClassPathRestartStrategy.class);
|
||||
ClassPathFileChangeListener listener = new ClassPathFileChangeListener(
|
||||
eventPublisher, restartStrategy);
|
||||
this.eventPublisher, this.restartStrategy, this.fileSystemWatcher);
|
||||
File folder = new File("s1");
|
||||
File file = new File("f1");
|
||||
ChangedFile file1 = new ChangedFile(folder, file, ChangedFile.Type.ADD);
|
||||
|
|
@ -99,10 +108,10 @@ public class ClassPathFileChangeListenerTests {
|
|||
ChangedFiles changedFiles = new ChangedFiles(new File("source"), files);
|
||||
Set<ChangedFiles> changeSet = Collections.singleton(changedFiles);
|
||||
if (restart) {
|
||||
given(restartStrategy.isRestartRequired(file2)).willReturn(true);
|
||||
given(this.restartStrategy.isRestartRequired(file2)).willReturn(true);
|
||||
}
|
||||
listener.onChange(changeSet);
|
||||
verify(eventPublisher).publishEvent(this.eventCaptor.capture());
|
||||
verify(this.eventPublisher).publishEvent(this.eventCaptor.capture());
|
||||
ClassPathChangedEvent actualEvent = (ClassPathChangedEvent) this.eventCaptor
|
||||
.getValue();
|
||||
assertThat(actualEvent.getChangeSet(), equalTo(changeSet));
|
||||
|
|
|
|||
|
|
@ -28,11 +28,9 @@ import org.junit.Test;
|
|||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher;
|
||||
import org.springframework.boot.devtools.classpath.ClassPathRestartStrategy;
|
||||
import org.springframework.boot.devtools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcher;
|
||||
import org.springframework.boot.devtools.filewatch.FileSystemWatcherFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
|
@ -43,6 +41,7 @@ import org.springframework.util.FileCopyUtils;
|
|||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClassPathFileSystemWatcher}.
|
||||
|
|
@ -62,7 +61,8 @@ public class ClassPathFileSystemWatcherTests {
|
|||
this.thrown.expect(IllegalArgumentException.class);
|
||||
this.thrown.expectMessage("Urls must not be null");
|
||||
URL[] urls = null;
|
||||
new ClassPathFileSystemWatcher(urls);
|
||||
new ClassPathFileSystemWatcher(mock(FileSystemWatcherFactory.class),
|
||||
mock(ClassPathRestartStrategy.class), urls);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -99,7 +99,8 @@ public class ClassPathFileSystemWatcherTests {
|
|||
public ClassPathFileSystemWatcher watcher() {
|
||||
FileSystemWatcher watcher = new FileSystemWatcher(false, 100, 10);
|
||||
URL[] urls = this.environemnt.getProperty("urls", URL[].class);
|
||||
return new ClassPathFileSystemWatcher(watcher, restartStrategy(), urls);
|
||||
return new ClassPathFileSystemWatcher(new MockFileSystemWatcherFactory(
|
||||
watcher), restartStrategy(), urls);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
@ -136,4 +137,19 @@ public class ClassPathFileSystemWatcherTests {
|
|||
|
||||
}
|
||||
|
||||
private static class MockFileSystemWatcherFactory implements FileSystemWatcherFactory {
|
||||
|
||||
private final FileSystemWatcher watcher;
|
||||
|
||||
public MockFileSystemWatcherFactory(FileSystemWatcher watcher) {
|
||||
this.watcher = watcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystemWatcher getFileSystemWatcher() {
|
||||
return this.watcher;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,11 +29,9 @@ import org.junit.Test;
|
|||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.devtools.restart.RestartInitializer;
|
||||
import org.springframework.boot.devtools.restart.Restarter;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFile.Kind;
|
||||
import org.springframework.boot.devtools.restart.classloader.ClassLoaderFiles;
|
||||
import org.springframework.boot.test.OutputCapture;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
|
@ -250,10 +248,10 @@ public class RestarterTests {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void restart() {
|
||||
public void restart(FailureHandler failureHandler) {
|
||||
try {
|
||||
stop();
|
||||
start();
|
||||
start(failureHandler);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
|
|
@ -261,8 +259,9 @@ public class RestarterTests {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void relaunch(ClassLoader classLoader) throws Exception {
|
||||
protected Throwable relaunch(ClassLoader classLoader) throws Exception {
|
||||
this.relaunchClassLoader = classLoader;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Reference in New Issue