Add resource hints for configuration properties
This commits introduces a RuntimeHintsRegistrar for configuration properties. By default, it provides the necessary hint to load application properties and yaml files in default locations. Closes gh-31311pull/31400/head
parent
47b980a687
commit
d5695c1931
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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.context.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.aot.hint.support.FilePatternResourceHintsRegistrar;
|
||||
import org.springframework.boot.env.PropertySourceLoader;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* {@link RuntimeHintsRegistrar} implementation for application configuration.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 3.0
|
||||
* @see FilePatternResourceHintsRegistrar
|
||||
*/
|
||||
public class ConfigDataLocationRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ConfigDataLocationRuntimeHintsRegistrar.class);
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
List<String> fileNames = getFileNames(classLoader);
|
||||
List<String> locations = getLocations(classLoader);
|
||||
List<String> extensions = getExtensions(classLoader);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Registering application configuration hints for " + fileNames + "(" + extensions + ") at "
|
||||
+ locations);
|
||||
}
|
||||
new FilePatternResourceHintsRegistrar(fileNames, locations, extensions).registerHints(hints.resources(),
|
||||
classLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application file names to consider.
|
||||
* @param classLoader the classloader to use
|
||||
* @return the configuration file names
|
||||
*/
|
||||
protected List<String> getFileNames(ClassLoader classLoader) {
|
||||
return Arrays.asList(StandardConfigDataLocationResolver.DEFAULT_CONFIG_NAMES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the locations to consider. A location is a classpath location that may or may
|
||||
* not use the standard {@code classpath:} prefix.
|
||||
* @param classLoader the classloader to use
|
||||
* @return the configuration file locations
|
||||
*/
|
||||
protected List<String> getLocations(ClassLoader classLoader) {
|
||||
List<String> classpathLocations = new ArrayList<>();
|
||||
for (ConfigDataLocation candidate : ConfigDataEnvironment.DEFAULT_SEARCH_LOCATIONS) {
|
||||
for (ConfigDataLocation configDataLocation : candidate.split()) {
|
||||
String location = configDataLocation.getValue();
|
||||
if (location.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
|
||||
classpathLocations.add(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classpathLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application file extensions to consider. A valid extension starts with a
|
||||
* dot.
|
||||
* @param classLoader the classloader to use
|
||||
* @return the configuration file extensions
|
||||
*/
|
||||
protected List<String> getExtensions(ClassLoader classLoader) {
|
||||
List<String> extensions = new ArrayList<>();
|
||||
List<PropertySourceLoader> propertySourceLoaders = getSpringFactoriesLoader(classLoader)
|
||||
.load(PropertySourceLoader.class);
|
||||
for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) {
|
||||
for (String fileExtension : propertySourceLoader.getFileExtensions()) {
|
||||
String candidate = "." + fileExtension;
|
||||
if (!extensions.contains(candidate)) {
|
||||
extensions.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
protected SpringFactoriesLoader getSpringFactoriesLoader(ClassLoader classLoader) {
|
||||
return SpringFactoriesLoader.forDefaultResourceLocation(classLoader);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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.context.config;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.ResourcePatternHint;
|
||||
import org.springframework.aot.hint.ResourcePatternHints;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.boot.env.PropertiesPropertySourceLoader;
|
||||
import org.springframework.boot.env.PropertySourceLoader;
|
||||
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||
import org.springframework.boot.testsupport.mock.MockSpringFactoriesLoader;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConfigDataLocationRuntimeHintsRegistrar}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ConfigDataLocationRuntimeHintsRegistrarTests {
|
||||
|
||||
@Test
|
||||
void registerWithDefaultSettings() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
new TestConfigDataLocationRuntimeHintsRegistrar().registerHints(hints, null);
|
||||
assertThat(hints.resources().resourcePatterns()).singleElement()
|
||||
.satisfies(includes("application*.properties", "application*.xml", "application*.yaml",
|
||||
"application*.yml", "config/application*.properties", "config/application*.xml",
|
||||
"config/application*.yaml", "config/application*.yml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithCustomName() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
new TestConfigDataLocationRuntimeHintsRegistrar() {
|
||||
@Override
|
||||
protected List<String> getFileNames(ClassLoader classLoader) {
|
||||
return List.of("test");
|
||||
}
|
||||
|
||||
}.registerHints(hints, null);
|
||||
assertThat(hints.resources().resourcePatterns()).singleElement()
|
||||
.satisfies(includes("test*.properties", "test*.xml", "test*.yaml", "test*.yml",
|
||||
"config/test*.properties", "config/test*.xml", "config/test*.yaml", "config/test*.yml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithCustomLocation() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
new TestConfigDataLocationRuntimeHintsRegistrar() {
|
||||
@Override
|
||||
protected List<String> getLocations(ClassLoader classLoader) {
|
||||
return List.of("config/");
|
||||
}
|
||||
}.registerHints(hints, null);
|
||||
assertThat(hints.resources().resourcePatterns()).singleElement()
|
||||
.satisfies(includes("config/application*.properties", "config/application*.xml",
|
||||
"config/application*.yaml", "config/application*.yml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithCustomExtension() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
new ConfigDataLocationRuntimeHintsRegistrar() {
|
||||
@Override
|
||||
protected List<String> getExtensions(ClassLoader classLoader) {
|
||||
return List.of(".conf");
|
||||
}
|
||||
}.registerHints(hints, null);
|
||||
assertThat(hints.resources().resourcePatterns()).singleElement()
|
||||
.satisfies(includes("application*.conf", "config/application*.conf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerWithUnknownLocationDoesNotAddHint() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
new ConfigDataLocationRuntimeHintsRegistrar() {
|
||||
@Override
|
||||
protected List<String> getLocations(ClassLoader classLoader) {
|
||||
return List.of(UUID.randomUUID().toString());
|
||||
}
|
||||
}.registerHints(hints, null);
|
||||
assertThat(hints.resources().resourcePatterns()).isEmpty();
|
||||
}
|
||||
|
||||
private Consumer<ResourcePatternHints> includes(String... patterns) {
|
||||
return (hint) -> {
|
||||
assertThat(hint.getIncludes().stream().map(ResourcePatternHint::getPattern))
|
||||
.containsExactlyInAnyOrder(patterns);
|
||||
assertThat(hint.getExcludes()).isEmpty();
|
||||
};
|
||||
}
|
||||
|
||||
static class TestConfigDataLocationRuntimeHintsRegistrar extends ConfigDataLocationRuntimeHintsRegistrar {
|
||||
|
||||
private final MockSpringFactoriesLoader springFactoriesLoader;
|
||||
|
||||
TestConfigDataLocationRuntimeHintsRegistrar(MockSpringFactoriesLoader springFactoriesLoader) {
|
||||
this.springFactoriesLoader = springFactoriesLoader;
|
||||
}
|
||||
|
||||
TestConfigDataLocationRuntimeHintsRegistrar() {
|
||||
this(new MockSpringFactoriesLoader().add(PropertySourceLoader.class, PropertiesPropertySourceLoader.class,
|
||||
YamlPropertySourceLoader.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SpringFactoriesLoader getSpringFactoriesLoader(ClassLoader classLoader) {
|
||||
return this.springFactoriesLoader;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue