Add Restart auto-configuration
Add auto-configuration for application Restarts. Restarts are enabled by default (when not running from a fat jar) and will be triggered when any classpath folder changes. The ClassPathRestartStrategy additional customization of when a full restart is required. By default a PatternClassPathRestartStrategy with patterns loaded from DeveloperToolsProperties. Closes gh-3084
This commit is contained in:
@@ -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.developertools.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Configuration properties for developer tools.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.developertools")
|
||||
public class DeveloperToolsProperties {
|
||||
|
||||
private static final String DEFAULT_RESTART_EXCLUDES = "META-INF/resources/**,resource/**,static/**,public/**,templates/**";
|
||||
|
||||
private Restart restart = new Restart();
|
||||
|
||||
public Restart getRestart() {
|
||||
return this.restart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart properties
|
||||
*/
|
||||
public static class Restart {
|
||||
|
||||
/**
|
||||
* Enable automatic restart.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Patterns that should be excluding for triggering a full restart.
|
||||
*/
|
||||
private String exclude = DEFAULT_RESTART_EXCLUDES;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getExclude() {
|
||||
return this.exclude;
|
||||
}
|
||||
|
||||
public void setExclude(String exclude) {
|
||||
this.exclude = exclude;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,10 +16,22 @@
|
||||
|
||||
package org.springframework.boot.developertools.autoconfigure;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.developertools.classpath.ClassPathChangedEvent;
|
||||
import org.springframework.boot.developertools.classpath.ClassPathFileSystemWatcher;
|
||||
import org.springframework.boot.developertools.classpath.ClassPathRestartStrategy;
|
||||
import org.springframework.boot.developertools.classpath.PatternClassPathRestartStrategy;
|
||||
import org.springframework.boot.developertools.restart.ConditionalOnInitializedRestarter;
|
||||
import org.springframework.boot.developertools.restart.Restarter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for local development support.
|
||||
@@ -29,11 +41,47 @@ import org.springframework.context.annotation.Configuration;
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnInitializedRestarter
|
||||
@EnableConfigurationProperties(DeveloperToolsProperties.class)
|
||||
public class LocalDeveloperToolsAutoConfiguration {
|
||||
|
||||
@Autowired
|
||||
private DeveloperToolsProperties properties;
|
||||
|
||||
@Bean
|
||||
public static LocalDeveloperPropertyDefaultsPostProcessor localDeveloperPropertyDefaultsPostProcessor() {
|
||||
return new LocalDeveloperPropertyDefaultsPostProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Local Restart Configuration.
|
||||
*/
|
||||
@ConditionalOnProperty(prefix = "spring.developertools.restart", name = "enabled", matchIfMissing = true)
|
||||
static class RestartConfiguration {
|
||||
|
||||
@Autowired
|
||||
private DeveloperToolsProperties properties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ClassPathFileSystemWatcher classPathFileSystemWatcher() {
|
||||
URL[] urls = Restarter.getInstance().getInitialUrls();
|
||||
return new ClassPathFileSystemWatcher(classPathRestartStrategy(), urls);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ClassPathRestartStrategy classPathRestartStrategy() {
|
||||
return new PatternClassPathRestartStrategy(this.properties.getRestart()
|
||||
.getExclude());
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onClassPathChanged(ClassPathChangedEvent event) {
|
||||
if (event.isRestartRequired()) {
|
||||
Restarter.getInstance().restart();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.developertools.classpath;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.developertools.filewatch.ChangedFiles;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ApplicationEvent} containing details of a classpath change.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
* @see ClassPathFileChangeListener
|
||||
*/
|
||||
public class ClassPathChangedEvent extends ApplicationEvent {
|
||||
|
||||
private final Set<ChangedFiles> changeSet;
|
||||
|
||||
private final boolean restartRequired;
|
||||
|
||||
/**
|
||||
* Create a new {@link ClassPathChangedEvent}.
|
||||
* @param source the source of the event
|
||||
* @param changeSet the changed files
|
||||
* @param restartRequired if a restart is required due to the change
|
||||
*/
|
||||
public ClassPathChangedEvent(Object source, Set<ChangedFiles> changeSet,
|
||||
boolean restartRequired) {
|
||||
super(source);
|
||||
Assert.notNull(changeSet, "ChangeSet must not be null");
|
||||
this.changeSet = changeSet;
|
||||
this.restartRequired = restartRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return details of the files that changed.
|
||||
* @return the changed files
|
||||
*/
|
||||
public Set<ChangedFiles> getChangeSet() {
|
||||
return this.changeSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if an application restart is required due to the change.
|
||||
* @return if an application restart is required
|
||||
*/
|
||||
public boolean isRestartRequired() {
|
||||
return this.restartRequired;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.developertools.classpath;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.developertools.filewatch.ChangedFile;
|
||||
import org.springframework.boot.developertools.filewatch.ChangedFiles;
|
||||
import org.springframework.boot.developertools.filewatch.FileChangeListener;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link FileChangeListener} to publish {@link ClassPathChangedEvent
|
||||
* ClassPathChangedEvents}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
* @see ClassPathFileSystemWatcher
|
||||
*/
|
||||
public class ClassPathFileChangeListener implements FileChangeListener {
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
private final ClassPathRestartStrategy restartStrategy;
|
||||
|
||||
/**
|
||||
* Create a new {@link ClassPathFileChangeListener} instance.
|
||||
* @param eventPublisher the event publisher used send events
|
||||
* @param restartStrategy the restart strategy to use
|
||||
*/
|
||||
public ClassPathFileChangeListener(ApplicationEventPublisher eventPublisher,
|
||||
ClassPathRestartStrategy restartStrategy) {
|
||||
Assert.notNull(eventPublisher, "EventPublisher must not be null");
|
||||
Assert.notNull(restartStrategy, "RestartStrategy must not be null");
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.restartStrategy = restartStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(Set<ChangedFiles> changeSet) {
|
||||
boolean restart = isRestartRequired(changeSet);
|
||||
ApplicationEvent event = new ClassPathChangedEvent(this, changeSet, restart);
|
||||
this.eventPublisher.publishEvent(event);
|
||||
}
|
||||
|
||||
private boolean isRestartRequired(Set<ChangedFiles> changeSet) {
|
||||
for (ChangedFiles changedFiles : changeSet) {
|
||||
for (ChangedFile changedFile : changedFiles) {
|
||||
if (this.restartStrategy.isRestartRequired(changedFile)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.developertools.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.developertools.filewatch.FileSystemWatcher;
|
||||
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
|
||||
* changes.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
* @see ClassPathFileChangeListener
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ClassPathFileSystemWatcher} instance.
|
||||
* @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
|
||||
*/
|
||||
protected ClassPathFileSystemWatcher(FileSystemWatcher fileSystemWatcher,
|
||||
ClassPathRestartStrategy restartStrategy, URL[] urls) {
|
||||
Assert.notNull(fileSystemWatcher, "FileSystemWatcher must not be null");
|
||||
Assert.notNull(urls, "Urls must not be null");
|
||||
this.fileSystemWatcher = new FileSystemWatcher();
|
||||
this.restartStrategy = restartStrategy;
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (this.restartStrategy != null) {
|
||||
this.fileSystemWatcher.addListener(new ClassPathFileChangeListener(
|
||||
this.applicationContext, this.restartStrategy));
|
||||
}
|
||||
this.fileSystemWatcher.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
this.fileSystemWatcher.stop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.developertools.classpath;
|
||||
|
||||
import org.springframework.boot.developertools.filewatch.ChangedFile;
|
||||
|
||||
/**
|
||||
* Strategy interface used to determine when a changed classpath file should trigger a
|
||||
* full application restart. For example, static web resources might not require a full
|
||||
* restart where as class files would.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
* @see PatternClassPathRestartStrategy
|
||||
*/
|
||||
public interface ClassPathRestartStrategy {
|
||||
|
||||
/**
|
||||
* Return true if a full restart is required.
|
||||
* @param file the changed file
|
||||
* @return {@code true} if a full restart is required
|
||||
*/
|
||||
boolean isRestartRequired(ChangedFile file);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.developertools.classpath;
|
||||
|
||||
import org.springframework.boot.developertools.filewatch.ChangedFile;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Ant style pattern based {@link ClassPathRestartStrategy}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
* @see ClassPathRestartStrategy
|
||||
*/
|
||||
public class PatternClassPathRestartStrategy implements ClassPathRestartStrategy {
|
||||
|
||||
private final AntPathMatcher matcher = new AntPathMatcher();
|
||||
|
||||
private final String[] excludePatterns;
|
||||
|
||||
public PatternClassPathRestartStrategy(String excludePatterns) {
|
||||
this.excludePatterns = StringUtils
|
||||
.commaDelimitedListToStringArray(excludePatterns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRestartRequired(ChangedFile file) {
|
||||
for (String pattern : this.excludePatterns) {
|
||||
if (this.matcher.match(pattern, file.getRelativeName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support for classpath monitoring
|
||||
*/
|
||||
package org.springframework.boot.developertools.classpath;
|
||||
|
||||
Reference in New Issue
Block a user