diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java index 2471f19136d1..aefcc8cee35b 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/DevToolsProperties.java @@ -30,6 +30,7 @@ * * @author Phillip Webb * @author Stephane Nicoll + * @author Akshay Dubey * @since 1.3.0 */ @ConfigurationProperties(prefix = "spring.devtools") @@ -198,6 +199,22 @@ public static class Livereload { */ private int port = 35729; + /** + * Additional paths to watch for changes. + */ + private List 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; } @@ -214,6 +231,30 @@ public void setPort(int port) { this.port = port; } + public List getAdditionalPaths() { + return this.additionalPaths; + } + + public void setAdditionalPaths(List 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; + } + } } diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java index 86043b6b6ce7..48cddaf34478 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfiguration.java @@ -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; @@ -57,6 +59,7 @@ * @author Phillip Webb * @author Andy Wilkinson * @author Vladimir Tsanev + * @author Akshay Dubey * @since 1.3.0 */ @AutoConfiguration @@ -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(); + } + } /** @@ -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 staticLocations, + FileSystemWatcher fileSystemWatcher) { + this.fileSystemWatcher = fileSystemWatcher; + + for (File path : staticLocations) { + this.fileSystemWatcher.addSourceDirectory(path.getAbsoluteFile()); + } + + this.fileSystemWatcher.addListener((__) -> liveReloadServer.triggerReload()); + + this.fileSystemWatcher.start(); + } + + } + } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java index 478e6ff28388..d66f892b29e0 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/LocalDevToolsAutoConfigurationTests.java @@ -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; @@ -70,6 +71,7 @@ * @author Phillip Webb * @author Andy Wilkinson * @author Vladimir Tsanev + * @author Akshay Dubey */ @ExtendWith(MockRestarter.class) class LocalDevToolsAutoConfigurationTests { @@ -213,6 +215,21 @@ void watchingAdditionalPaths() throws Exception { .containsKey(new File("src/test/java").getAbsoluteFile()); } + @Test + void watchingAdditionalPathsForReload() throws Exception { + Map 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 directories = (Map) 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));