Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
*
* @author Phillip Webb
* @author Stephane Nicoll
* @author Akshay Dubey
* @since 1.3.0
*/
@ConfigurationProperties(prefix = "spring.devtools")
Expand Down Expand Up @@ -198,6 +199,22 @@ public static class Livereload {
*/
private int port = 35729;

/**
* Additional paths to watch for changes.
*/
private List<File> additionalPaths = new ArrayList<>();

/**
* Amount of time to wait between polling for classpath changes.
*/
private Duration pollInterval = Duration.ofSeconds(1);

/**
* Amount of quiet time required without any classpath changes before a reload is
* triggered.
*/
private Duration quietPeriod = Duration.ofMillis(400);

public boolean isEnabled() {
return this.enabled;
}
Expand All @@ -214,6 +231,30 @@ public void setPort(int port) {
this.port = port;
}

public List<File> getAdditionalPaths() {
return this.additionalPaths;
}

public void setAdditionalPaths(List<File> additionalPaths) {
this.additionalPaths = additionalPaths;
}

public Duration getPollInterval() {
return this.pollInterval;
}

public void setPollInterval(Duration pollInterval) {
this.pollInterval = pollInterval;
}

public Duration getQuietPeriod() {
return this.quietPeriod;
}

public void setQuietPeriod(Duration quietPeriod) {
this.quietPeriod = quietPeriod;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand Down Expand Up @@ -57,6 +59,7 @@
* @author Phillip Webb
* @author Andy Wilkinson
* @author Vladimir Tsanev
* @author Akshay Dubey
* @since 1.3.0
*/
@AutoConfiguration
Expand Down Expand Up @@ -89,6 +92,26 @@ LiveReloadServerEventListener liveReloadServerEventListener(OptionalLiveReloadSe
return new LiveReloadServerEventListener(liveReloadServer);
}

@Bean
LiveReloadForAdditionalPaths liveReloadForAdditionalPaths(LiveReloadServer liveReloadServer,
DevToolsProperties properties, FileSystemWatcher fileSystemWatcher) {
return new LiveReloadForAdditionalPaths(liveReloadServer, properties.getLivereload().getAdditionalPaths(),
fileSystemWatcher);
}

@Bean
@ConditionalOnMissingBean(RestartConfiguration.class)
FileSystemWatcher newFileSystemWatcher(DevToolsProperties properties) {
return new FileSystemWatcher(true, properties.getLivereload().getPollInterval(),
properties.getLivereload().getQuietPeriod());
}

@Bean
@ConditionalOnBean(RestartConfiguration.class)
FileSystemWatcher fileSystemWatcher(RestartConfiguration restartConfiguration) {
return restartConfiguration.newFileSystemWatcher();
}

}

/**
Expand Down Expand Up @@ -216,4 +239,30 @@ public void onApplicationEvent(ClassPathChangedEvent event) {

}

static class LiveReloadForAdditionalPaths implements DisposableBean {

private final FileSystemWatcher fileSystemWatcher;

@Override
public void destroy() throws Exception {
if (this.fileSystemWatcher != null) {
this.fileSystemWatcher.stop();
}
}

LiveReloadForAdditionalPaths(LiveReloadServer liveReloadServer, List<File> staticLocations,
FileSystemWatcher fileSystemWatcher) {
this.fileSystemWatcher = fileSystemWatcher;

for (File path : staticLocations) {
this.fileSystemWatcher.addSourceDirectory(path.getAbsoluteFile());
}

this.fileSystemWatcher.addListener((__) -> liveReloadServer.triggerReload());

this.fileSystemWatcher.start();
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration.LiveReloadForAdditionalPaths;
import org.springframework.boot.devtools.classpath.ClassPathChangedEvent;
import org.springframework.boot.devtools.classpath.ClassPathFileSystemWatcher;
import org.springframework.boot.devtools.livereload.LiveReloadServer;
Expand Down Expand Up @@ -70,6 +71,7 @@
* @author Phillip Webb
* @author Andy Wilkinson
* @author Vladimir Tsanev
* @author Akshay Dubey
*/
@ExtendWith(MockRestarter.class)
class LocalDevToolsAutoConfigurationTests {
Expand Down Expand Up @@ -213,6 +215,21 @@ void watchingAdditionalPaths() throws Exception {
.containsKey(new File("src/test/java").getAbsoluteFile());
}

@Test
void watchingAdditionalPathsForReload() throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put("spring.devtools.livereload.additional-paths", "src/main/java,src/test/java");
this.context = getContext(() -> initializeAndRun(Config.class, properties));
LiveReloadForAdditionalPaths liveReloadForAdditionalPaths = this.context
.getBean(LiveReloadForAdditionalPaths.class);
Object watcher = ReflectionTestUtils.getField(liveReloadForAdditionalPaths, "fileSystemWatcher");
@SuppressWarnings("unchecked")
Map<File, Object> directories = (Map<File, Object>) ReflectionTestUtils.getField(watcher, "directories");
assertThat(directories).hasSize(2)
.containsKey(new File("src/main/java").getAbsoluteFile())
.containsKey(new File("src/test/java").getAbsoluteFile());
}

@Test
void devToolsSwitchesJspServletToDevelopmentMode() throws Exception {
this.context = getContext(() -> initializeAndRun(Config.class));
Expand Down