From 48b5b6a24c7cb9da77787c3c7d3bd4eac2882f5c Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Fri, 23 Aug 2019 14:33:04 -0700 Subject: [PATCH] Polish "Added support for devtools YAML configuration" See gh-17915 --- .../DevToolsHomePropertiesPostProcessor.java | 53 ++++---- ...ToolsHomePropertiesPostProcessorTests.java | 116 +++++++----------- .../main/asciidoc/spring-boot-features.adoc | 2 +- .../src/main/asciidoc/using-spring-boot.adoc | 20 ++- 4 files changed, 89 insertions(+), 102 deletions(-) diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java index 8400be2bde..3105966224 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java @@ -18,14 +18,17 @@ package org.springframework.boot.devtools.env; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; -import java.util.logging.Logger; +import java.util.function.Function; import org.springframework.boot.SpringApplication; import org.springframework.boot.devtools.DevToolsEnablementDeducer; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.util.StringUtils; @@ -37,46 +40,52 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @author Andy Wilkinson * @author HaiTao Zhang + * @author Madhura Bhave * @since 1.3.0 */ public class DevToolsHomePropertiesPostProcessor implements EnvironmentPostProcessor { + private static final String LEGACY_FILE_NAME = ".spring-boot-devtools.properties"; + private static final String[] FILE_NAMES = new String[] { ".spring-boot-devtools.yml", ".spring-boot-devtools.yaml", ".spring-boot-devtools.properties" }; - private Logger logger = Logger.getLogger(getClass().getName()); + private static final String CONFIG_PATH = "/.config/spring-boot/"; @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { if (DevToolsEnablementDeducer.shouldEnable(Thread.currentThread())) { - File home = getHomeFolder(); - Properties properties = processDir(home, "/.config/spring-boot/", environment); - if (properties.isEmpty()) { - processDir(home, "", environment); + List propertySources = getPropertySources(); + if (propertySources.isEmpty()) { + addPropertySource(LEGACY_FILE_NAME, (file) -> "devtools-local", propertySources); } + propertySources.forEach((source) -> environment.getPropertySources().addFirst(source)); } } - private Properties processDir(File home, String configPath, ConfigurableEnvironment environment) { - Properties properties = new Properties(); + private List getPropertySources() { + List propertySources = new ArrayList<>(); for (String fileName : FILE_NAMES) { - File propertyFile = (home != null) ? new File(home, configPath + fileName) : null; - if (propertyFile != null && propertyFile.exists() && propertyFile.isFile()) { - addProperty(propertyFile, environment, fileName, properties); - } + addPropertySource(CONFIG_PATH + fileName, (file) -> "devtools-local: [" + file.toURI() + "]", + propertySources); } - return properties; + return propertySources; } - private void addProperty(File propertyFile, ConfigurableEnvironment environment, String fileName, - Properties properties) { - FileSystemResource resource = new FileSystemResource(propertyFile); - try { - PropertiesLoaderUtils.fillProperties(properties, resource); - environment.getPropertySources().addFirst(new PropertiesPropertySource("devtools-local", properties)); - } - catch (IOException ex) { - throw new IllegalStateException("Unable to load " + fileName, ex); + private void addPropertySource(String fileName, Function propertySourceName, + List propertySources) { + Properties properties; + File home = getHomeFolder(); + File propertyFile = (home != null) ? new File(home, fileName) : null; + if (propertyFile != null && propertyFile.exists() && propertyFile.isFile()) { + FileSystemResource resource = new FileSystemResource(propertyFile); + try { + properties = PropertiesLoaderUtils.loadProperties(resource); + propertySources.add(new PropertiesPropertySource(propertySourceName.apply(propertyFile), properties)); + } + catch (IOException ex) { + throw new IllegalStateException("Unable to load " + fileName, ex); + } } } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java index 0d87a3ac68..fe76b39246 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java @@ -37,97 +37,60 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Phillip Webb * @author Andy Wilkinson * @author HaiTao Zhang + * @author Madhura Bhave */ class DevToolsHomePropertiesPostProcessorTests { + private String configDir; + private File home; @BeforeEach - void setup(@TempDir File tempDir) throws IOException { + void setup(@TempDir File tempDir) { this.home = tempDir; + this.configDir = this.home + "/.config/spring-boot/"; + new File(this.configDir).mkdirs(); } @Test void loadsPropertiesFromHomeFolderUsingProperties() throws Exception { Properties properties = new Properties(); properties.put("abc", "def"); - OutputStream out = new FileOutputStream(new File(this.home, ".spring-boot-devtools.properties")); - properties.store(out, null); - out.close(); - ConfigurableEnvironment environment = new MockEnvironment(); - MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); - assertThat(environment.getProperty("abc")).isEqualTo("def"); - } - - @Test - void loadsPropertiesFromHomeFolderUsingYml() throws Exception { - Properties properties = new Properties(); - properties.put("abc", "def"); - OutputStream out = new FileOutputStream(new File(this.home, ".spring-boot-devtools.yml")); - properties.store(out, null); - out.close(); - ConfigurableEnvironment environment = new MockEnvironment(); - MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); - assertThat(environment.getProperty("abc")).isEqualTo("def"); - } - - @Test - void loadsPropertiesFromHomeFolderUsingYaml() throws Exception { - Properties properties = new Properties(); - properties.put("abc", "def"); - OutputStream out = new FileOutputStream(new File(this.home, ".spring-boot-devtools.yaml")); - properties.store(out, null); - out.close(); - ConfigurableEnvironment environment = new MockEnvironment(); - MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); + writeFile(properties, ".spring-boot-devtools.properties"); + ConfigurableEnvironment environment = getPostProcessedEnvironment(); assertThat(environment.getProperty("abc")).isEqualTo("def"); } @Test void loadsPropertiesFromConfigFolderUsingProperties() throws Exception { Properties properties = new Properties(); - new File(this.home + "/.config/spring-boot").mkdirs(); properties.put("abc", "def"); - OutputStream out = new FileOutputStream( - new File(this.home + "/.config/spring-boot", ".spring-boot-devtools.properties")); + OutputStream out = new FileOutputStream(new File(this.configDir, ".spring-boot-devtools.properties")); properties.store(out, null); out.close(); - ConfigurableEnvironment environment = new MockEnvironment(); - MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); + ConfigurableEnvironment environment = getPostProcessedEnvironment(); assertThat(environment.getProperty("abc")).isEqualTo("def"); } @Test void loadsPropertiesFromConfigFolderUsingYml() throws Exception { Properties properties = new Properties(); - new File(this.home + "/.config/spring-boot").mkdirs(); properties.put("abc", "def"); - OutputStream out = new FileOutputStream( - new File(this.home + "/.config/spring-boot", ".spring-boot-devtools.yml")); + OutputStream out = new FileOutputStream(new File(this.configDir, ".spring-boot-devtools.yml")); properties.store(out, null); out.close(); - ConfigurableEnvironment environment = new MockEnvironment(); - MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); + ConfigurableEnvironment environment = getPostProcessedEnvironment(); assertThat(environment.getProperty("abc")).isEqualTo("def"); } @Test void loadsPropertiesFromConfigFolderUsingYaml() throws Exception { Properties properties = new Properties(); - new File(this.home + "/.config/spring-boot").mkdirs(); properties.put("abc", "def"); - OutputStream out = new FileOutputStream( - new File(this.home + "/.config/spring-boot", ".spring-boot-devtools.yaml")); + OutputStream out = new FileOutputStream(new File(this.configDir, ".spring-boot-devtools.yaml")); properties.store(out, null); out.close(); - ConfigurableEnvironment environment = new MockEnvironment(); - MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); + ConfigurableEnvironment environment = getPostProcessedEnvironment(); assertThat(environment.getProperty("abc")).isEqualTo("def"); } @@ -135,7 +98,7 @@ class DevToolsHomePropertiesPostProcessorTests { void loadFromConfigFolderWithPropertiesTakingPrecedence() throws Exception { Properties properties = new Properties(); properties.put("abc", "def"); - new File(this.home + "/.config/spring-boot").mkdirs(); + properties.put("bar", "baz"); OutputStream out = new FileOutputStream( new File(this.home + "/.config/spring-boot/", ".spring-boot-devtools.yaml")); properties.store(out, null); @@ -146,57 +109,62 @@ class DevToolsHomePropertiesPostProcessorTests { new File(this.home + "/.config/spring-boot/", ".spring-boot-devtools.properties")); properties2.store(out2, null); out2.close(); - ConfigurableEnvironment environment = new MockEnvironment(); - MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); + ConfigurableEnvironment environment = getPostProcessedEnvironment(); assertThat(environment.getProperty("abc")).isEqualTo("jkl"); + assertThat(environment.getProperty("bar")).isEqualTo("baz"); } @Test - void loadFromHomeFolderWithPropertiesTakingPrecedence() throws Exception { + void loadFromConfigFolderTakesPrecedenceOverHomeFolder() throws Exception { Properties properties = new Properties(); properties.put("abc", "def"); - new File(this.home + "/.config/spring-boot").mkdirs(); - OutputStream out = new FileOutputStream(new File(this.home, ".spring-boot-devtools.yaml")); - properties.store(out, null); - out.close(); + properties.put("bar", "baz"); + writeFile(properties, ".spring-boot-devtools.properties"); Properties properties2 = new Properties(); properties2.put("abc", "jkl"); - OutputStream out2 = new FileOutputStream(new File(this.home, ".spring-boot-devtools.properties")); + OutputStream out2 = new FileOutputStream( + new File(this.home + "/.config/spring-boot/", ".spring-boot-devtools.properties")); properties2.store(out2, null); out2.close(); - ConfigurableEnvironment environment = new MockEnvironment(); - MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); + ConfigurableEnvironment environment = getPostProcessedEnvironment(); assertThat(environment.getProperty("abc")).isEqualTo("jkl"); + assertThat(environment.getProperty("bar")).isEqualTo(null); } @Test - void loadFromConfigFolderTakesPrecedenceOverHomeFolder() throws Exception { + void loadFromConfigFolderWithYamlTakesPrecedenceOverHomeFolder() throws Exception { Properties properties = new Properties(); properties.put("abc", "def"); - new File(this.home + "/.config/spring-boot").mkdirs(); - OutputStream out = new FileOutputStream(new File(this.home, ".spring-boot-devtools.properties")); - properties.store(out, null); - out.close(); + properties.put("bar", "baz"); + writeFile(properties, ".spring-boot-devtools.properties"); Properties properties2 = new Properties(); properties2.put("abc", "jkl"); OutputStream out2 = new FileOutputStream( - new File(this.home + "/.config/spring-boot/", ".spring-boot-devtools.properties")); + new File(this.home + "/.config/spring-boot/", ".spring-boot-devtools.yml")); properties2.store(out2, null); out2.close(); - ConfigurableEnvironment environment = new MockEnvironment(); - MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); - runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); + ConfigurableEnvironment environment = getPostProcessedEnvironment(); assertThat(environment.getProperty("abc")).isEqualTo("jkl"); + assertThat(environment.getProperty("bar")).isEqualTo(null); } @Test void ignoresMissingHomeProperties() throws Exception { + ConfigurableEnvironment environment = getPostProcessedEnvironment(); + assertThat(environment.getProperty("abc")).isNull(); + } + + private void writeFile(Properties properties, String s) throws IOException { + OutputStream out = new FileOutputStream(new File(this.home, s)); + properties.store(out, null); + out.close(); + } + + private ConfigurableEnvironment getPostProcessedEnvironment() throws Exception { ConfigurableEnvironment environment = new MockEnvironment(); MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); - assertThat(environment.getProperty("abc")).isNull(); + return environment; } protected void runPostProcessor(Runnable runnable) throws Exception { diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 684b802549..11811e7cc6 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -431,7 +431,7 @@ Spring Boot uses a very particular `PropertySource` order that is designed to al sensible overriding of values. Properties are considered in the following order: . <> -on your home directory (`~/.spring-boot-devtools.properties` when devtools is active). +in the `$HOME/.config/spring-boot` folder when devtools is active. . {spring-javadoc}/test/context/TestPropertySource.{dc-ext}[`@TestPropertySource`] annotations on your tests. . `properties` attribute on your tests. Available on diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 241eff6797..c791c4fa4a 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -1018,20 +1018,30 @@ from your IDE, only the first has LiveReload support. [[using-boot-devtools-globalsettings]] === Global Settings -You can configure global devtools settings by adding a file named -`.spring-boot-devtools.properties` to your `$HOME` folder (note that the filename starts -with "`.`"). Any properties added to this file apply to _all_ Spring Boot applications on +You can configure global devtools settings by adding any of the following files to the `$HOME/.config/spring-boot` +folder (note that the filenames startswith "`.`"): + +. `.spring-boot-devtools.properties` +. `.spring-boot-devtools.yaml` +. `.spring-boot-devtools.yml` + +Any properties added to these file apply to _all_ Spring Boot applications on your machine that use devtools. For example, to configure restart to always use a <>, you would add the following property: -.~/.spring-boot-devtools.properties +.~/config/spring-boot/.spring-boot-devtools.properties [source,properties,indent=0] ---- spring.devtools.reload.trigger-file=.reloadtrigger ---- -NOTE: Profiles activated in `.spring-boot-devtools.properties` will not affect the +NOTE: If devtools configuration files are not found in `$HOME/.config/spring-boot`, the root of the `$HOME` folder +is searched for the presence of a `.spring-boot-devtools.properties` file. This allows you to share the devtools global +configuration with applications that are on an older version of Spring Boot that does not support the `$HOME/.config/spring-boot` +location. + +NOTE: Profiles activated in the above files will not affect the loading of <>.