@PropertySource 不覆盖在 application.properties 中定义的属性。

huangapple go评论154阅读模式
英文:

@PropertySource doesn't override properties defined in application.properties

问题

如果在 application.properties 中定义了任何属性,就不能通过在其他 .properties 文件中重新定义它来覆盖它,即使使用 @PropertySource 注解。只有在 application.properties 中存在原始值时,才会出现这个 bug。任何其他文件(例如 app.properties)都可以成功覆盖其值。

例如:

application.properties

test.application=original

app.properties

test.app=original

override.properties

test.application=overridden
test.app=overridden

ApplicationPropertiesConfig.java

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@Data
@PropertySource(value = {
        "classpath:application.properties",
        "classpath:app.properties",
        "classpath:override.properties"
})
@ConfigurationProperties(prefix = "test")
public class ApplicationPropertiesConfig {

    private String application;  // == "original"   (BAD)

    private String app;          // == "overridden" (GOOD)

}

在上面的配置中,字段 application 在 bean 创建时不会被覆盖,并将保留在 application.properties 中定义的原始值。

这已经在最新的 Spring Boot 版本中确认过:2.7.11 和 3.0.6。

即使在 @PropertySource 下没有列出 application.properties,或者使用了 @PropertySources 注解,也会观察到相同的行为。

复现此问题的代码在这里:https://github.com/denisab85/spring-property-overriding

一个已知的解决方法是将 application.properties 重命名为其他名称(例如 app.properties)。然后覆盖工作正常。但由于项目的规模很大,许多类已经依赖于默认的 application.properties,因此在我的情况下这不适用。

来源:Maksim Muruev(mmuruev)在 @PropertySource not overriding in order [SPR-13500] 中的评论。

英文:

If any property is defined in application.properties, it cannot be overridden by re-defining it in other .properties files using the @PropertySource annotation. This bug is only valid if the original value is present in application.properties. Any other file (e.g. app.properties) will allow to successfully override its values.

E.g.:

application.properties:

test.application=original

app.properties:

test.app=original

override.properties:

test.application=overridden
test.app=overridden

ApplicationPropertiesConfig.java:

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@Data
@PropertySource(value = {
        "classpath:application.properties",
        "classpath:app.properties",
        "classpath:override.properties"
})
@ConfigurationProperties(prefix = "test")
public class ApplicationPropertiesConfig {

    private String application;  // == "original"   (BAD)

    private String app;          // == "overridden" (GOOD)

}

In the configuration above, the field application won't be overridden upon bean creation and will keep its original value defined in application.properties.

This was confirmed with the most recent Spring Boot versions: 2.7.11 and 3.0.6.

The same behavior is observed even if application.properties is not listed under @PropertySource or if the @PropertySources annotation is used.

Code reproducing the issue is here: https://github.com/denisab85/spring-property-overriding

>! A known workaround is renaming application.properties to something else (e.g. app.properties). Overriding works normally then. But this will not work in my case due to the size of the project where many classes already rely on the default application.properties.
>!
>! Source: a comment by Maksim Muruev (mmuruev) in @PropertySource not overriding in order [SPR-13500].

答案1

得分: 3

以下是您要翻译的内容:

问题在于您正在使用Spring Boot而不是纯粹的Spring。Spring Boot具有某些有争议的观点。正如我在评论中提到的,您不应该使用@PropertySource,而应该通过--spring.config.additional-locations来指定额外的配置文件,或者(我没有提到的)通过EnvironmentPostProcessor将它们添加进去。

如果您将Environment注入到测试中,并从中获取PropertySources,然后可以将它们打印到控制台上。然后您将看到以下内容。

ConfigurationPropertySourcesPropertySource {name='configurationProperties'}
MapPropertySource {name='test'}
MapPropertySource {name='Inlined Test Properties'}
PropertiesPropertySource {name='systemProperties'}
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.properties]' via location 'optional:classpath:/''}
ResourcePropertySource {name='class path resource [override.properties]'}
ResourcePropertySource {name='class path resource [app.properties]'}
ResourcePropertySource {name='class path resource [application.properties]'}

这是不管是否使用@ConfigurationProperties都一样。Spring Boot总是在任何其他内容之前加载application.properties文件,因此始终具有最高优先级,这是由于Spring Boot的原因。

现在,正如我所述,您可以通过在启动时添加配置位置来绕过此问题,例如添加--spring.config.additional-location=classpath:app.properties,classpath:override.properties,如果您这样做,输出将更改为以下内容:

ConfigurationPropertySourcesPropertySource {name='configurationProperties'}
MapPropertySource {name='test'}
MapPropertySource {name='Inlined Test Properties'}
SimpleCommandLinePropertySource {name='commandLineArgs'}
PropertiesPropertySource {name='systemProperties'}
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [override.properties]' via location 'classpath:override.properties''}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [app.properties]' via location 'classpath:app.properties''}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.properties]' via location 'optional:classpath:/''}

现在,您的测试将通过(并且您将看到覆盖将起作用)。

最后,您还可以使用EnvironmentPostProcessor将这些文件添加为属性源,并在spring.factories文件中指定它,以便它们将自动加载。

package com.example.springpropertyoverriding;

import java.io.IOException;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.support.ResourcePropertySource;

public class AdditionalConfigurationFilesAdder implements EnvironmentPostProcessor {

	private static final String[] locations =
			new String[] { "classpath:app.properties", "classpath:override.properties"};

	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		MutablePropertySources propertySources = environment.getPropertySources();
		for (String location : locations) {
			try {
				ResourcePropertySource source = new ResourcePropertySource(location);
				propertySources.addAfter("random", source);
			} catch (IOException ex) {
				throw new IllegalStateException(ex);
			}
		}
	}
}

现在,您可以在META-INF/spring.factories文件中引用它,以便它将添加这些资源。

org.springframework.boot.env.EnvironmentPostProcessor=com.example.springpropertyoverriding.AdditionalConfigurationFilesAdder

现在,再次运行测试,输出将与指定--spring.config.additional-location时的输出(或多或少)相同,并且测试将通过。

您必须意识到您正在使用Spring Boot,它对何时加载什么具有一些有争议的观点。实际上,顺序也在Spring Boot文档中有解释。其中清楚地显示了application.properties始终优先于@PropertySource

Spring Boot使用非常特定的PropertySource顺序,旨在允许合理覆盖值。后来的属性源可以覆盖先前定义的值。按以下顺序考虑来源:

  1. 默认属性(通过设置SpringApplication.setDefaultProperties指定)。
  2. @PropertySource注解在您的@Configuration类上。请注意,此类属性源在应用程序上下文正在刷新时才会添加到Environment中。这太晚了,无法在刷新开始之前配置某些属性,例如logging.*spring.main.*,这些属性在刷新之前被读取。
  3. 配置数据(例如application.properties文件)。
  4. 仅在random.*.中具有属性的RandomValuePropertySource
  5. 操作系统环境变量。
  6. Java系统属性(System.getProperties())。
  7. 来自java:comp/env的JNDI属性。
  8. ServletContext初始化参数。
  9. ServletConfig初始化参数。
  10. 来自SPRING_APPLICATION_JSON的属性(嵌入在环境变量或系统属性中的内联JSON)。
  11. 命令行参数。
  12. 在测试上的属性。可在@SpringBootTest以及用于测试特定应用程序片段的测试注释上使用。
  13. 测试上的@TestPropertySource注解
英文:

The problem is that you are using Spring Boot and not plain Spring. Which has certain, opinionated views. As I mentioned in my comment you shouldn't be using @PropertySource but specify the additional configuration files through --spring.config.additional-locations or (which I didn't mention) add them through an EnvironmentPostProcessor.

If you would inject the Environment into your test and obtain the PropertySources from it you can print them out to the console. You will then see the following.

ConfigurationPropertySourcesPropertySource {name='configurationProperties'}
MapPropertySource {name='test'}
MapPropertySource {name='Inlined Test Properties'}
PropertiesPropertySource {name='systemProperties'}
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.properties]' via location 'optional:classpath:/''}
ResourcePropertySource {name='class path resource [override.properties]'}
ResourcePropertySource {name='class path resource [app.properties]'}
ResourcePropertySource {name='class path resource [application.properties]'}

This is, regardless of using @ConfigurationProperties. Spring Boot will always load the application.properties before anything else, so before the @PropertySources in some @Configuration files (or where ever you choose to place them). So this file always takes precedence, due to Spring Boot.

Now as I stated you can circumvent this by either specifying configuration locations at startup by adding --spring.config.additional-location=classpath:app.properties,classpath:override.properties if you do that the output will change to the following:

ConfigurationPropertySourcesPropertySource {name='configurationProperties'}
MapPropertySource {name='test'}
MapPropertySource {name='Inlined Test Properties'}
SimpleCommandLinePropertySource {name='commandLineArgs'}
PropertiesPropertySource {name='systemProperties'}
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [override.properties]' via location 'classpath:override.properties''}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [app.properties]' via location 'classpath:app.properties''}
OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.properties]' via location 'optional:classpath:/''}

And now your tests will pass (and you will see the overrides will work).

Finally you could also use an EnvironmentPostProcessor to add these files as property sources and have them load automatically by specifying it in a spring.factories file.

package com.example.springpropertyoverriding;

import java.io.IOException;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.support.ResourcePropertySource;

public class AdditionalConfigurationFilesAdder implements EnvironmentPostProcessor {

	private static final String[] locations =
			new String[] { "classpath:app.properties", "classpath:override.properties"};

	@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		MutablePropertySources propertySources = environment.getPropertySources();
		for (String location : locations) {
			try {
				ResourcePropertySource source = new ResourcePropertySource(location);
				propertySources.addAfter("random", source);
			} catch (IOException ex) {
				throw new IllegalStateException(ex);
			}
		}
	}
}

Now you can reference this in a META-INF/spring.factories file so that it will add the resources.

org.springframework.boot.env.EnvironmentPostProcessor=com.example.springpropertyoverriding.AdditionalConfigurationFilesAdder

And now again when running the tests the output will be (more or less) the same as with specifying --spring.config.additional-location and the tests will pass.

You have to realize the fact that you are using Spring Boot and that comes with some opinionated views on what to load when. The order is actually also explained in the Spring Boot Documentation. Which clearly shows that application.properties always takes precedence over @PropertySource.

> Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Later property sources can override the values defined in earlier ones. Sources are considered in the following order:
>
>1. Default properties (specified by setting SpringApplication.setDefaultProperties).
>2. @PropertySource annotations on your @Configuration classes. Please note that such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.* which are read before refresh begins.
>3. Config data (such as application.properties files).
>4. A RandomValuePropertySource that has properties only in random.*.
>5. OS environment variables.
>6. Java System properties (System.getProperties()).
>7. JNDI attributes from java:comp/env.
>8. ServletContext init parameters.
>9. ServletConfig init parameters.
>10. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
>11. Command line arguments.
>12.properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
>13. @TestPropertySource annotations on your tests.
>14. Devtools global settings properties in the $HOME/.config/spring-boot directory when devtools is active.
>Config data files are considered in the following order:

>1. Application properties packaged inside your jar (application.properties and YAML variants).
>2. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
>3. Application properties outside of your packaged jar (application.properties and YAML variants).
>4. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).

So in short no it is not a bug it is described and intended behavior you are seeing when using Spring Boot.

huangapple
  • 本文由 发表于 2023年5月11日 08:51:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76223453.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定