我正在查看位于here
的spring-boot文档具体是关于考虑财产的顺序的部分:
更具体地说:
打包在jar中的特定于配置文件的应用程序属性(application- {profile} .properties和YAML变体)
首先我要提一下,使用这种方法加载配置文件特定配置时没有任何问题(前提是文件位于类路径中:/或classpath:/ config。
但是,我希望做的是实现如下的约定:
classpath:/default/application.properties
classpath:/{profile}/application.properties
此外,我希望在不使用spring.config.location
属性的情况下实现此配置。我对Spring Boot很陌生,所以我正在寻找一些提示,告诉我如何实现这个约定。基于我的研究似乎可以通过添加自定义ConfigFileApplicationListener来实现。如果这是一个明智的起点或任何其他可能更好的想法,请告诉我。
更新:
似乎如果我可以以编程方式构建spring.config.location
属性列表,我可以在类路径中传递:/ default,classpath:{profile}。基于spring.profiles.active环境变量。以下ConfigFileApplicationListener看起来就像我要调用的那样:
public void setSearchLocations(String locations)
但是,我不确定生命周期中的哪个位置会打电话。
答案 0 :(得分:3)
所以这就是我设法提出的,不确定我是否会使用此解决方案,但我想我会提供它以防有任何有用的反馈。
所以我尝试在将ConfigFileApplicationListener
添加到SpringApplication之后但在触发之前尝试将setSearchLocations(String locations)方法设置为调用。我这样做是通过添加一个也实现Ordered的新侦听器并确保它在ConfigFileApplicationListener
之前运行。这似乎做我想要的,但我仍然认为有一个更优雅的方法。我特别不喜欢迭代听众。
public class LocationsSettingConfigFileApplicationListener implements
ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
/**
* this should run before ConfigFileApplicationListener so it can set its
* state accordingly
*/
@Override
public int getOrder() {
return ConfigFileApplicationListener.DEFAULT_ORDER - 1;
}
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
SpringApplication app = event.getSpringApplication();
ConfigurableEnvironment env = event.getEnvironment();
for (ApplicationListener<?> listener : app.getListeners()) {
if (listener instanceof ConfigFileApplicationListener) {
ConfigFileApplicationListener cfal = (ConfigFileApplicationListener) listener;
//getSearchLocations omitted
cfal.setSearchLocations(getSearchLocations(env));
}
}
}
答案 1 :(得分:0)
我们使用EnvironmentPostProcessor做了类似的事情来实现以下命名约定:
某些应用程序没有自己的&lt; appName&gt; ;那些在主类的静态初始化程序中调用setApplicationName
来使用这两个附加文件的人。
这里的hacky部分是我们不排除默认ConfigFileApplicationListener
,而是通过删除PropertySource ConfigFileApplicationListener.APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME
来撤消它。
package example.foo.utils.spring;
import static org.springframework.core.env.AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME;
import java.io.IOException;
import java.util.List;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.env.PropertySourcesLoader;
import org.springframework.boot.logging.LoggingApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Configures environment properties according to the FOO conventions.
*/
public class FooPropertiesEnvPostProcessor implements EnvironmentPostProcessor, Ordered {
/**
* Order before LoggingApplicationListener and before
* AutowiredAnnotationBeanPostProcessor. The position relative to
* ConfigFileApplicationListener (which we want to override) should not
* matter: If it runs before this, we remove its PropertySource; otherwise,
* its PropertySource remains but should do no harm as it is added at the
* end.
*/
public static final int ORDER
= Math.min(LoggingApplicationListener.DEFAULT_ORDER, new AutowiredAnnotationBeanPostProcessor().getOrder()) - 1;
static {
System.setProperty(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME,
System.getProperty(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, "production"));
}
public FooPropertiesEnvPostProcessor() {
}
/**
* Property key used as the application (sub-project) specific part in
* properties file names.
* <p>
* <strong>Note:</strong> Direct access to this property key is meant for
* tests which set the property in an annotation (e.g.
* {@link IntegrationTest}). However, SpringBootApplications which need to
* set this system property before Spring initialization should call
* {@link #setApplicationName(String) setApplicationName} instead.
* </p>
*/
public static final String APP_KEY = "foo.config.name";
/**
* Sets the application name used to find property files (using
* {@link FooPropertiesEnvPostProcessor}).
*
* @param appName
* the application name
*/
public static void setApplicationName(String appName) {
System.setProperty(APP_KEY, appName);
}
/**
* Replacement for logging, which is not yet initialized during
* postProcessEnvironment.
*/
static void log(String format, Object... args) {
System.out.println(String.format(format, args));
}
static void debug(PropertyResolver env, String format, Object... args) {
String level = env.getProperty("logging.level." + FooPropertiesEnvPostProcessor.class.getName());
if ("trace".equalsIgnoreCase(level) || "debug".equalsIgnoreCase(level)) {
log(format, args);
}
}
static void trace(PropertyResolver env, String format, Object... args) {
String level = env.getProperty("logging.level." + FooPropertiesEnvPostProcessor.class.getName());
if ("trace".equalsIgnoreCase(level)) {
log(format, args);
}
}
@Override
public int getOrder() {
return ORDER;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addProperties(environment.getPropertySources(), application.getResourceLoader(), environment);
}
public static void addProperties(MutablePropertySources propSources, ResourceLoader resLoader, ConfigurableEnvironment propRes) {
trace(propRes, "FooPropertiesEnvPostProcessor.addProperties(..)");
List<PropertySourceLoader> psls = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
PropertySourcesLoader.class.getClassLoader());
// ResourcePatternUtils does not accept null yet
// (https://jira.spring.io/browse/SPR-14500)
ResourcePatternResolver rpr = resLoader != null ? ResourcePatternUtils.getResourcePatternResolver(resLoader)
: new PathMatchingResourcePatternResolver();
final String suffix = ".properties"; // SonarQube made me declare this
String[] profiles = propRes.getActiveProfiles();
if (profiles.length == 0) {
profiles = new String[] { System.getProperty(DEFAULT_PROFILES_PROPERTY_NAME) };
}
// ConfigFileApplicationListener adds PropertySource "applicationConfigurationProperties" consisting of
// - "applicationConfig: [classpath:/${spring.config.name}-<profile>.properties]"
// - "applicationConfig: [classpath:/${spring.config.name}.properties]"
// Since we want the profile to have higher priority than the app name, we cannot just set
// "spring.config.name" to the app name, use ConfigFileApplicationListener, and add
// "application-<profile>.properties" and "application.properties".
// Instead, remove ConfigFileApplicationListener:
PropertySource<?> removedPropSource = propSources.remove(ConfigFileApplicationListener.APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME);
trace(propRes, "removed %s from %s", removedPropSource, propSources);
// add meta.properties at last position, then others before the previously added. => resulting order:
// - { systemProperties
// - systemEnvironment
// - random } - already added automatically elsewhere
// - file:${foo.home}/foo-<profile>.properties
// - classpath:<appName>-<profile>.properties
// - classpath:application-<profile>.properties
// - classpath:<appName>.properties
// - classpath:application.properties
// - classpath:meta.properties
// By adding ${foo.home}/... (chronlogically) last, the property can be set in the previously added resources.
boolean defaultAppName = "application".equals(propRes.resolveRequiredPlaceholders("${" + APP_KEY + ":application}"));
String psn = null;
psn = addProperties(propSources, propRes, rpr, psls, true, psn, propRes.resolveRequiredPlaceholders("classpath*:meta" + suffix));
psn = addProperties(propSources, propRes, rpr, psls, true, psn, propRes.resolveRequiredPlaceholders("classpath*:application" + suffix));
if (!defaultAppName) {
psn = addProperties(propSources, propRes, rpr, psls, false,
psn, propRes.resolveRequiredPlaceholders("classpath*:${" + APP_KEY + ":application}" + suffix));
}
for (String profile : profiles) {
psn = addProperties(propSources, propRes, rpr, psls, false, psn,
propRes.resolveRequiredPlaceholders("classpath*:application-" + profile + suffix));
}
if (!defaultAppName) {
for (String profile : profiles) {
psn = addProperties(propSources, propRes, rpr, psls, false,
psn, propRes.resolveRequiredPlaceholders("classpath*:${" + APP_KEY + ":application}-" + profile + suffix));
}
}
for (String profile : profiles) {
psn = addProperties(propSources, propRes, rpr, psls, false,
psn, propRes.resolveRequiredPlaceholders("file:${foo.home:.}/foo-" + profile + suffix));
}
Stream<PropertySource<?>> propSourcesStream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(propSources.iterator(), 0), false);
debug(propRes, "Property sources: %s%n", propSourcesStream.map(PropertySource::getName).collect(Collectors.joining(", ")));
}
/**
* Adds a resource given by location string to the given PropertySources, if
* it exists.
*
* @param propSources
* the property sources to modify
* @param successorName
* the name of the (already added) successor resource, i.e. the
* resource before which the new one should be added; if null,
* add as last resource
* @param location
* the location of the resource to add
* @return the name of the newly added resource, or {@code successorName} if
* not added
*/
private static String addProperties(MutablePropertySources propSources, PropertyResolver propRes, ResourcePatternResolver resLoader,
List<PropertySourceLoader> propLoaders, boolean required, String successorName, String location) {
Resource[] resources;
try {
resources = resLoader.getResources(location);
} catch (IOException e) {
throw new IllegalStateException("failed to load property source " + location + ": " + e, e);
}
if (resources.length == 0) {
debug(propRes, "%s property resource not found: %s", required ? "required" : "optional", location);
if (required) {
throw new IllegalStateException("required property source " + location + " not found");
} else {
return successorName;
}
}
String newSuccessorName = successorName;
for (Resource resource : resources) {
boolean exists = resource.exists();
debug(propRes, "%s property resource %sfound: %s%s", required ? "required" : "optional", exists ? "" : "not ", location,
uriDescription(resource, propRes));
if (!required && !exists) {
continue;
}
boolean loaded = false;
for (PropertySourceLoader propLoader : propLoaders) {
if (canLoadFileExtension(propLoader, resource)) {
newSuccessorName = addResource(propSources, propRes, resource, propLoader, newSuccessorName);
loaded = true;
break;
}
}
if (!loaded && required) {
throw new IllegalStateException("No PropertySourceLoader found to load " + resource);
}
}
return newSuccessorName;
}
private static String addResource(MutablePropertySources propSources, PropertyResolver propRes, Resource resource,
PropertySourceLoader propLoader, String successorName) {
try {
PropertySource<?> propSource = propLoader.load(resource.getDescription(), resource, null);
if (propSource == null) {
// e.g. a properties file with everything commented;
// org.springframework.boot.env.PropertiesPropertySourceLoader
// converts empty to null
return successorName;
}
if (successorName == null) {
propSources.addLast(propSource);
} else if (successorName.equals(propSource.getName())) {
// happens if APP_KEY is not set, so that
// "${APP_KEY:application}" == "application"
trace(propRes, "skipping duplicate resource %s", successorName);
} else {
propSources.addBefore(successorName, propSource);
}
return propSource.getName();
} catch (IOException e) {
throw new IllegalStateException("Unable to load configuration file " + resource + ": " + e, e);
}
}
/**
* Stolen from {@link PropertySourcesLoader}
*/
private static boolean canLoadFileExtension(PropertySourceLoader loader, Resource resource) {
String filename = resource.getFilename().toLowerCase();
for (String extension : loader.getFileExtensions()) {
if (filename.endsWith("." + extension.toLowerCase())) {
return true;
}
}
return false;
}
private static String uriDescription(Resource resource, PropertyResolver propRes) {
try {
return resource.exists() ? (" in " + resource.getURI()) : "";
} catch (IOException e) {
trace(propRes, "getURI: %s", e);
return "";
}
}
}
org.springframework.boot.env.EnvironmentPostProcessor = example.foo.utils.spring.FooPropertiesEnvPostProcessor
要在测试中获得相同的属性,他们有@ContextConfiguration(..., initializers = TestAppContextInitializer.class)
。
TestAppContextInitializer
实施ApplicationContextInitializer<GenericApplicationContext>
并致电
FooPropertiesEnvPostProcessor.addProperties
方法中的initialize
。
不幸的是,EnvironmentPostProcessor似乎也默认缺少Spring Shell。在我们的情况下(因为只有一小部分
部分应用程序使用Spring Shell),限制<context:component-scan base-package=.../>
就足够了
META-INF/spring/spring-shell-plugin.xml
中的范围只包含不需要EnvironmentPostProcessor设置的任何属性的东西。
答案 2 :(得分:0)
# override from outer config , eg. java -jar --spring.profiles.active=your config
spring.profiles.active=dev
spring.config.location=classpath:/${spring.profiles.active}/application.properties
答案 3 :(得分:0)
不需要编写新类的解决方案:
public static void main(String[] args) {
SpringApplication app = new SpringApplication();
app.getListeners().stream()
.filter(listener -> listener instanceof ConfigFileApplicationListener)
.forEach(configListener -> {
((ConfigFileApplicationListener) configListener).setSearchLocations(mySearchLocations);
((ConfigFileApplicationListener) configListener).setSearchNames(mySearchNames);
});
app.setSources(singleton(MyClassName.class));
app.run(args);
}