Spring Boot allows us to replace our application.properties files with YAML equivalents. However, I seem to hit a snag with my tests. If I annotate my TestConfiguration (a simple Java config), it is expecting a properties file.
For example this doesn't work:
#PropertySource(value = "classpath:application-test.yml")
If I have this in my YAML file:
db:
url: jdbc:oracle:thin:#pathToMyDb
username: someUser
password: fakePassword
And I'd be leveraging those values with something like this:
#Value("${db.username}") String username
However, I end up with an error like so:
Could not resolve placeholder 'db.username' in string value "${db.username}"
How can I leverage the YAML goodness in my tests as well?
Spring-boot has a helper for this, just add
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
at the top of your test classes or an abstract test superclass.
Edit: I wrote this answer five years ago. It doesn't work with recent versions of Spring Boot. This is what I do now (please translate the Kotlin to Java if necessary):
#TestPropertySource(locations=["classpath:application.yml"])
#ContextConfiguration(
initializers=[ConfigFileApplicationContextInitializer::class]
)
is added to the top, then
#Configuration
open class TestConfig {
#Bean
open fun propertiesResolver(): PropertySourcesPlaceholderConfigurer {
return PropertySourcesPlaceholderConfigurer()
}
}
to the context.
As it was mentioned #PropertySource doesn't load yaml file. As a workaround load the file on your own and add loaded properties to Environment.
Implemement ApplicationContextInitializer:
public class YamlFileApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Resource resource = applicationContext.getResource("classpath:file.yml");
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Add your initializer to your test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
#Test
public test(){
// test your properties
}
}
#PropertySource can be configured by factory argument. So you can do something like:
#PropertySource(value = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class)
Where YamlPropertyLoaderFactory is your custom property loader:
public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
if (resource == null){
return super.createPropertySource(name, resource);
}
return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null);
}
}
Inspired by https://stackoverflow.com/a/45882447/4527110
Another option is to set the spring.config.location through #TestPropertySource:
#TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }
#PropertySource only supports properties files (it's a limitation from Spring, not Boot itself). Feel free to open a feature request ticket in JIRA.
From Spring Boot 1.4, you can use the new #SpringBootTest annotation to achieve this more easily (and to simplify your integration test setup in general) by bootstrapping your integration tests using Spring Boot support.
Details on the Spring Blog.
As far as I can tell, this means you get all the benefits of Spring Boot's externalized config goodness just like in your production code, including automatically picking up YAML config from the classpath.
By default, this annotation will
... first attempt to load #Configuration from any inner-classes, and if that fails, it will search for your primary #SpringBootApplication class.
but you can specify other configuration classes if required.
For this particular case, you can combine #SpringBootTest with #ActiveProfiles( "test" ) and Spring will pick up your YAML config, provided it follows the normal Boot naming standards (i.e. application-test.yml).
#RunWith( SpringRunner.class )
#SpringBootTest
#ActiveProfiles( "test" )
public class SpringBootITest {
#Value("${db.username}")
private String username;
#Autowired
private MyBean myBean;
...
}
Note: SpringRunner.class is the new name for SpringJUnit4ClassRunner.class
The approach to loading the yaml properties, IMHO can be done in two ways:
a. You can put the configuration in a standard location - application.yml in the classpath root - typically src/main/resources and this yaml property should automatically get loaded by Spring boot with the flattened path name that you have mentioned.
b. The second approach is a little more extensive, basically define a class to hold your properties this way:
#ConfigurationProperties(path="classpath:/appprops.yml", name="db")
public class DbProperties {
private String url;
private String username;
private String password;
...
}
So essentially this is saying that load the yaml file and populate the DbProperties class based on the root element of "db".
Now to use it in any class you will have to do this:
#EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {
#Autowired private DbProperties dbProperties;
}
Either of these approaches should work for you cleanly using Spring-boot.
Since Spring Boot 2.4.0 you can use ConfigDataApplicationContextInitializer as follows:
#SpringJUnitConfig(
classes = { UserAccountPropertiesTest.TestConfig.class },
initializers = { ConfigDataApplicationContextInitializer.class }
)
class UserAccountPropertiesTest {
#Configuration
#EnableConfigurationProperties(UserAccountProperties.class)
static class TestConfig { }
#Autowired
UserAccountProperties userAccountProperties;
#Test
void getAccessTokenExpireIn() {
assertThat(userAccountProperties.getAccessTokenExpireIn()).isEqualTo(120);
}
#Test
void getRefreshTokenExpireIn() {
assertThat(userAccountProperties.getRefreshTokenExpireIn()).isEqualTo(604800);
}
}
See also: https://www.baeldung.com/spring-boot-testing-configurationproperties#YAML-binding
I found a workaround by using #ActiveProfiles("test") and adding an application-test.yml file to src/test/resources.
It ended up looking like this:
#SpringApplicationConfiguration(classes = Application.class, initializers = ConfigFileApplicationContextInitializer.class)
#ActiveProfiles("test")
public abstract class AbstractIntegrationTest extends AbstractTransactionalJUnit4SpringContextTests {
}
The file application-test.yml just contains the properties that I want to override from application.yml (which can be found in src/main/resources).
I have tried all of the listed questions, but all of them not work for my task: using specific yaml file for some unit test.
In my case, it works like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(initializers = {ConfigFileApplicationContextInitializer.class})
#TestPropertySource(properties = {"spring.config.location=file:../path/to/specific/config/application.yml"})
public class SomeTest {
#Value("${my.property.value:#{null}}")
private String value;
#Test
public void test() {
System.out.println("value = " + value);
}
}
it's because you have not configure snakeyml.
spring boot come with #EnableAutoConfiguration feature.
there is snakeyml config too when u call this annotation..
this is my way:
#Configuration
#EnableAutoConfiguration
public class AppContextTest {
}
here is my test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(
classes = {
AppContextTest.class,
JaxbConfiguration.class,
}
)
public class JaxbTest {
//tests are ommited
}
I needed to read some properties into my code and this works with spring-boot 1.3.0.RELEASE
#Autowired
private ConfigurableListableBeanFactory beanFactory;
// access a properties.yml file like properties
#Bean
public PropertySource properties() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("properties.yml"));
propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
// properties need to be processed by beanfactory to be accessible after
propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
return propertySourcesPlaceholderConfigurer.getAppliedPropertySources().get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME);
}
Loading custom yml file with multiple profile config in Spring Boot.
1) Add the property bean with SpringBootApplication start up as follows
#SpringBootApplication
#ComponentScan({"com.example.as.*"})
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
#Profile("dev")
public PropertySourcesPlaceholderConfigurer propertiesStage() {
return properties("dev");
}
#Bean
#Profile("stage")
public PropertySourcesPlaceholderConfigurer propertiesDev() {
return properties("stage");
}
#Bean
#Profile("default")
public PropertySourcesPlaceholderConfigurer propertiesDefault() {
return properties("default");
}
/**
* Update custom specific yml file with profile configuration.
* #param profile
* #return
*/
public static PropertySourcesPlaceholderConfigurer properties(String profile) {
PropertySourcesPlaceholderConfigurer propertyConfig = null;
YamlPropertiesFactoryBean yaml = null;
propertyConfig = new PropertySourcesPlaceholderConfigurer();
yaml = new YamlPropertiesFactoryBean();
yaml.setDocumentMatchers(new SpringProfileDocumentMatcher(profile));// load profile filter.
yaml.setResources(new ClassPathResource("env_config/test-service-config.yml"));
propertyConfig.setProperties(yaml.getObject());
return propertyConfig;
}
}
2) Config the Java pojo object as follows
#Component
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(Include.NON_NULL)
#ConfigurationProperties(prefix = "test-service")
public class TestConfig {
#JsonProperty("id")
private String id;
#JsonProperty("name")
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3) Create the custom yml (and place it under resource path as follows,
YML File name : test-service-config.yml
Eg Config in the yml file.
test-service:
id: default_id
name: Default application config
---
spring:
profiles: dev
test-service:
id: dev_id
name: dev application config
---
spring:
profiles: stage
test-service:
id: stage_id
name: stage application config
<dependency>
<groupId>com.github.yingzhuo</groupId>
<artifactId>spring-boot-stater-env</artifactId>
<version>0.0.3</version>
</dependency>
Welcome to use my library. Now yaml, toml, hocon is supported.
Source: github.com
This is not an answer to the original question, but an alternative solution for a need to have a different configuration in a test...
Instead of #PropertySource you can use -Dspring.config.additional-location=classpath:application-tests.yml.
Be aware, that suffix tests does not mean profile...
In that one YAML file one can specify multiple profiles, that can kind of inherit from each other, read more here - Property resolving for multiple Spring profiles (yaml configuration)
Then, you can specify in your test, that active profiles (using #ActiveProfiles("profile1,profile2")) are profile1,profile2 where profile2 will simply override (some, one does not need to override all) properties from profile1.
project demo url: https://github.com/Forest10/spring-boot-family/tree/spring-boot-with-yml
I run this answer in my prod env!!! so if you against this ans. please test first!!!
There is no need to add like YamlPropertyLoaderFactory or YamlFileApplicationContextInitializer. You should convert your idea
Follow these steps:
Just add applicationContext.xml like
#ImportResource({"classpath:applicationContext.xml"})
to your ApplicationMainClass.
and your applicationContext.xml should write like this
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
default-autowire="byName"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:property-placeholder location="classpath*:*.yml"/>
</beans>
This can help scan your application-test.yml
db:
url: jdbc:oracle:thin:#pathToMyDb
username: someUser
password: fakePassword
Enhancing Mateusz Balbus answer.
Modified YamlFileApplicationContextInitializer class where YAML location is defined per test class. It does not work per test, unfortunately.
public abstract class YamlFileApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
/***
* Return location of a YAML file, e.g.: classpath:file.yml
*
* #return YAML file location
*/
protected abstract String getResourceLocation();
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
Resource resource = applicationContext.getResource(getResourceLocation());
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Usage:
Create subclass of YamlFileApplicationContextInitializer with defined getResourceLocation() method and add this subclass into #SpringApplicationConfiguration annotation.
This way it is easiest to make the test class itself.
#RunWith(SpringRunner.class)
#SpringApplicationConfiguration(classes = Application.class, initializers = SimpleTest.class)
public class SimpleTest extends YamlFileApplicationContextInitializer {
#Override
protected String getResourceLocation() {
return "classpath:test_specific.yml";
}
#Test
public test(){
// test your properties
}
}
Here's an improved version of YamlPropertyLoaderFactory which supports the new PropertySource.ignoreResourceNotFound, based on this answer:
Java:
public final class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
private final YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader();
#NotNull
public PropertySource createPropertySource(
#Nullable String name,
#NotNull EncodedResource resource
) {
try {
String parsedName;
if (name != null && !name.equals(""))
parsedName = name;
else parsedName = resource.getResource().getFilename();
return yamlPropertySourceLoader.load(parsedName, resource.getResource()).get(0);
} catch (Exception e) {
Exception possibleFileNotFoundException = ExceptionUtils.throwableOfType(e, FileNotFoundException.class);
throw possibleFileNotFoundException != null ? possibleFileNotFoundException : e;
}
}
}
// Usage
#PropertySource(
value = {"file:./my-optional-config.yml"},
factory = YamlPropertyLoaderFactory.class,
ignoreResourceNotFound = true
)
Kotlin:
class YamlPropertyLoaderFactory : DefaultPropertySourceFactory() {
private val yamlPropertySourceLoader = YamlPropertySourceLoader()
override fun createPropertySource(
name: String?,
resource: EncodedResource
): PropertySource<*> = try {
(
yamlPropertySourceLoader.load(
if (name != null && name.isNotBlank()) name else resource.resource.filename,
resource.resource
)
)[0]
} catch (e: Exception) {
throw ExceptionUtils.throwableOfType(e, FileNotFoundException::class.java) ?: e
}
}
// Usage
#PropertySource(
value = ["file:/my-optional-config.yml"],
factory = YamlPropertyLoaderFactory::class,
ignoreResourceNotFound = true
)
Related
I have written a PropertySource that enables classpath: prefix for spring.kafka.properties.ssl.truststore.location (which out-of-the-box is not supported).
Essentially, this lets me place a truststore.jks inside my Spring Boot application's src/main/resources folder and reference it from inside the .jar file.
This works nicely for plain Spring Kafka configurations, like these:
spring:
kafka:
properties:
ssl.truststore.location: classpath:myTruststore.jks
It currently fails when the same configurations are given in the context of a Spring Cloud Stream Binder:
spring:
cloud:
stream:
binders:
my-binder:
type: kafka
environment:
spring:
kafka:
properties:
ssl.truststore.location: classpath:myTruststore.jks
My PropertySource is not even called back, when I would have expected it to be called with a poperty name of spring.cloud.stream.binders.my-binder.environment.spring.kafka.properties.ssl.truststore.location.
I think my PropertySource that would do the classpath: resolution is not part of the Environment of the given Spring Cloud Stream binder.
Question: how can one add PropertySources to a specific Binder's environment (or to all of them)?
Thanks!
EDIT
I add my PropertySource in a Spring Boot auto-configuration like this:
#Configuration
#AutoConfigureBefore(KafkaAutoConfiguration.class)
#ConditionalOnProperty(name = "com.acme.kafka.enabled", matchIfMissing = true)
#EnableConfigurationPropertiesAcmeKafkaConfigurations.class)
public class AcmeKafkaAutoConfiguration {
#Bean
ClasspathResourceSupportEnablingPropertySource acmeKafkaClasspathResourceEnablingPropertySource(ConfigurableEnvironment environment) throws IOException {
ClasspathResourcesSupport classpathResourcesSupport = new ClasspathResourcesSupport(Files.createTempDirectory(ACME_KAFKA_PREFIX));
ClasspathResourceSupportEnablingPropertySource propertySource
= new ClasspathResourceSupportEnablingPropertySource(ClasspathResourceSupportEnablingPropertySource.NAME, environment, classpathResourcesSupport);
environment.getPropertySources().addFirst(propertySource);
return propertySource;
}
}
EDIT NO.2: I tried out what Gary Russel suggested below (using a Bean Post Processor declared as a static bean method).
It works but in my case I get a lot of additional warning logs at startup of the form:
Bean '...' of type [...] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
According to this post this can cause some really nasty side effects.
Here is the code I was using (which caused the warnings above):
#Configuration
#AutoConfigureBefore(KafkaAutoConfiguration.class)
#ConditionalOnProperty(name = "com.acme.kafka.enabled", matchIfMissing = true)
#EnableConfigurationProperties(AcmeKafkaConfigurations.class)
public class AcmeKafkaAutoConfiguration {
private static final String ACME_KAFKA_PREFIX = "acme.kafka.";
#Bean
#ConditionalOnMissingBean
public static List<ConnectivityConfigurationsProvider> acmeKafkaTokenProviders(OAuth2TokenClient oAuthClient, AcmeKafkaConfigurations configuration) {
List<ConnectivityConfigurationsProvider> connectivityConfigurationsProviders = new ArrayList<>();
configuration.getInstances().forEach(serviceInstanceConfiguration -> {
TokenProvider tokenProvider = new DefaultOAuth2TokenProvider(oAuthClient, serviceInstanceConfiguration);
ConnectivityConfigurationsProvider connectivityConfigurationsProvider = new ConnectivityConfigurationsProvider(serviceInstanceConfiguration, tokenProvider);
connectivityConfigurationsProviders.add(connectivityConfigurationsProvider);
});
return connectivityConfigurationsProviders;
}
#Bean
#ConditionalOnMissingBean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static OAuth2TokenClient acmeKafkaOAuth2TokenClient() {
return new DefaultOAuth2TokenClient(new DefaultClientCredentialsTokenResponseClient());
}
#Bean
public static ConnectivityConfigurationsProviders acmeKafkaConnectivityConfigurationsProviders(AcmeKafkaConfigurations configuration, List<ConnectivityConfigurationsProvider> connectivityConfigurationsProviders) {
return new ConnectivityConfigurationsProviders(connectivityConfigurationsProviders);
}
#Bean
static NoOpBeanPostProcessor springKafkaConfigurationsPropertySource(ConfigurableEnvironment environment, ConnectivityConfigurationsProviders connectivityConfigurationsProviders) {
SpringKafkaConfigurationsPropertySource propertySource = new SpringKafkaConfigurationsPropertySource(SpringKafkaConfigurationsPropertySource.NAME, connectivityConfigurationsProviders);
environment.getPropertySources().addLast(propertySource);
return new NoOpBeanPostProcessor();
}
#Bean
#ConditionalOnClass(name = "org.springframework.cloud.stream.binder.BinderConfiguration")
static NoOpBeanPostProcessor springCloudStreamKafkaConfigurationsPropertySource(ConfigurableEnvironment environment, ConnectivityConfigurationsProviders connectivityConfigurationsProviders) {
SpringCloudStreamKafkaConfigurationsPropertySource propertySource = new SpringCloudStreamKafkaConfigurationsPropertySource(SpringCloudStreamKafkaConfigurationsPropertySource.NAME, connectivityConfigurationsProviders);
environment.getPropertySources().addLast(propertySource);
return new NoOpBeanPostProcessor();
}
#Bean
static NoOpBeanPostProcessor acmeKafkaConnectivityConfigurationsPropertySource(ConfigurableEnvironment environment, ConnectivityConfigurationsProviders connectivityConfigurationsProviders) {
AcmeKafkaConnectivityConfigurationsPropertySource propertySource = new AcmeKafkaConnectivityConfigurationsPropertySource(AcmeKafkaConnectivityConfigurationsPropertySource.NAME, connectivityConfigurationsProviders);
environment.getPropertySources().addLast(propertySource);
return new NoOpBeanPostProcessor();
}
#Bean
static NoOpBeanPostProcessor acmeKafkaClasspathResourceEnablingPropertySource(ConfigurableEnvironment environment) throws IOException {
ClasspathResourcesSupport classpathResourcesSupport = new ClasspathResourcesSupport(Files.createTempDirectory(ACME_KAFKA_PREFIX));
ClasspathResourceSupportEnablingPropertySource propertySource
= new ClasspathResourceSupportEnablingPropertySource(ClasspathResourceSupportEnablingPropertySource.NAME, environment, classpathResourcesSupport);
environment.getPropertySources().addFirst(propertySource);
return new NoOpBeanPostProcessor();
}
/**
* This BeanPostProcessor does not really post-process any beans.
* It is a way of getting the bean methods that add the property sources
* above to be called early enough in the lifecycle of Spring ApplicationContext
* creation.
*
* BeanPostProcessors are instantiated by Spring extremely early.
* #Bean methods providing them should be declared as static methods.
* See: https://stackoverflow.com/questions/30874244/bean-annotation-on-a-static-method
*/
static class NoOpBeanPostProcessor implements BeanPostProcessor, Ordered {
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
}
EDIT
This seems to work:
#SpringBootApplication
public class So61826877Application {
private static final String KEY = "spring.cloud.stream.binders.my-binder.environment."
+ "spring.kafka.properties.ssl.truststore.location";
public static void main(String[] args) {
SpringApplication.run(So61826877Application.class, args);
}
#Bean
public static BeanPostProcessor configureSource(ConfigurableEnvironment env) {
Properties source = new Properties();
System.out.println(env.getProperty(KEY));
source.setProperty(KEY, "/path/to/myTruststore.jks");
env.getPropertySources().addFirst(new PropertiesPropertySource("cp", source));
return new MyBpp();
}
#Bean
Consumer<String> input() {
return System.out::println;
}
}
class MyBpp implements BeanPostProcessor, Ordered {
#Override
public int getOrder() {
return Integer.MAX_VALUE;
}
}
classpath:myTruststore.jks
...
ConsumerConfig values:
allow.auto.create.topics = true
auto.commit.interval.ms = 100
...
ssl.truststore.location = /path/to/myTruststore.jks
...
I'd like to add a new property source that could be used to read property values in an application. I'd like to do this using Spring. I have a piece of code like this in a #Configuration class:
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();
MutablePropertySources sources = new MutablePropertySources();
MyCustomPropertySource propertySource = new MyCustomPropertySource("my custom property source");
sources.addFirst(propertySource);
properties.setPropertySources(sources);
return properties;
}
This seems to work pretty well. However, what it is also doing is overriding other property values (e.g. server.port property in application.properties file used by spring boot) which I don't want to overwrite. So the basic question is what's the best way to add this propertysource but not have it override other properties. Any way to grab the existing propertysources and simply add on to it?
I got this working by adding a custom initiailizer to my spring boot app:
#SpringBootApplication
public class MyApp {
public static void main(String[] args) {
new SpringApplicationBuilder(MyApp.class)
.initializers(new MyContextInitializer()) // <---- here
.run(args);
}
}
Where MyContextInitializer contains: -
public class MyContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
// Create map for properites and add first (important)
Map<String, Object> myProperties = new HashMap<>();
myProperties.put("some-prop", "custom-value");
environment.getPropertySources().addFirst(
new MapPropertySource("my-props", myProperties));
}
}
Note, if your application.yaml contains: -
some-prop: some-value
another-prop: this is ${some-prop} property
Then the initialize method will update the some-prop to custom-value and when the app loads it will have the following values at run-time:
some-prop: custom-value
another-prop: this is custom-value property
Note, if the initialize method did a simple System.setProperty call i.e.
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
System.setProperty("some-prop", "custom-value");
}
... then the another-prop would be equal to this is some-value property which is not what we generally want (and we lose the power of Spring config property resolution).
Try setting IgnoreUnresolvablePlaceholders to TRUE. I had a similar problem which I was able to resolve in this way. In my case, I had another placeholderconfigurer, which was working - but properties in the second one were not being resolved unless I set this property to TRUE.
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(Boolean.TRUE);
return propertySourcesPlaceholderConfigurer;
}
Yet another possibility (after lots of experimentation, it's what worked for me) would be to declare your PropertySource inside a ApplicationContextInitializer and then inject that one in your SpringBootServletInitializer:
public class MyPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final Logger logger = LoggerFactory.getLogger(ApplicationPropertyInitializer.class);
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
MyPropertySource ps = new MyPropertySource();
applicationContext.getEnvironment().getPropertySources().addFirst(ps);
}
}
public class MyInitializer extends SpringBootServletInitializer{
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return super.configure(builder.initializers(new MyPropertyInitializer()));
}
}
You can perhaps add your propertySource straight into environment once it is initialized.
EDIT: As this is done AFTER the class is processed, you cannot expect the #Value annotations to pick up anything from this particular PropertySource in the same #Configuration class - or any other that is loaded before.
#Configuration
public class YourPropertyConfigClass{
#Value("${fromCustomSource}")
String prop; // failing - property source not yet existing
#Autowired
ConfigurableEnvironment env;
#PostConstruct
public void init() throws Exception {
env.getPropertySources().addFirst(
new MyCustomPropertySource("my custom property source"));
}
}
#Configuration
#DependsOn("YourPropertyConfigClass")
public class PropertyUser {
#Value("${fromCustomSource}")
String prop; // not failing
}
You could move the #PostConstruct to a separate #Configuration class and mark other classes using those properties #DependOn("YourPropertyConfigClass") (this works - but perhaps there is a better way to force configuration order?).
Anyway - it is only worth it, if MyCustomPropertySource cannot be simply added using #PropertySource("file.properties") annotation - which would solve the problem for simple property files.
If you implement PropertySourceFactory as such:
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
public class CustomPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) {
...
}
}
You can use the following property source:
#PropertySource(name="custom-prop-source", value="", factory=CustomPropertySourceFactory.class)
Kind of hack-ish, but it works.
I'm writing integration test for my spring boot application but when I try to override some properties using #TestPropertySource, it's loading the property file defined in the context xml but it's not overriding the properties defined in the annotation.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {DefaultApp.class, MessageITCase.Config.class})
#WebAppConfiguration
#TestPropertySource(properties = {"spring.profiles.active=hornetq", "test.url=http://www.test.com/",
"test.api.key=343krqmekrfdaskfnajk"})
public class MessageITCase {
#Value("${test.url}")
private String testUrl;
#Value("${test.api.key}")
private String testApiKey;
#Test
public void testUrl() throws Exception {
System.out.println("Loaded test url:" + testUrl);
}
#Configuration
#ImportResource("classpath:/META-INF/spring/test-context.xml")
public static class Config {
}
}
I tested this feature with Spring Boot 1.4
Line below works pretty well
#TestPropertySource(properties = { "key=value", "eureka.client.enabled=false" })
Nevertheless new #SpringBootTest annotation is working as well
#RunWith(SpringRunner.class)
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = { "key=value", "eureka.client.enabled=false" }
)
public class NewBootTest {
#Value("${key}")
public String key;
#Test
public void test() {
System.out.println("great " + key);
}
}
I had a similar problem. I fixed it by updating the Spring Context beans.xml to use
org.springframework.context.support.PropertySourcesPlaceholderConfigurer instead of org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.
From the JavaDoc of PropertyPlaceholderConfigurer
Deprecated.
as of 5.2; use org.springframework.context.support.PropertySourcesPlaceholderConfigurer instead which is more flexible through taking advantage of the Environment and PropertySource mechanisms.
I have some spring beans which wire in property values using the #Value annotation.
e.g.
#Value("${my.property}")
private String myField;
Usually the values are sourced from property files.
The test I am currently writing uses a fully annotation based configuration.
e.g.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class AcceptanceTest implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Configuration
#ComponentScan(basePackages = {
"my.package.one",
"my.package.two"
})
static class ContextConfiguration {
#Bean
public MyBean getMyBean(){
return new MyBean();
}
}
#Autowired
private AnotherBean anotherBean;
#Test
public void testTest(){
assertNotNull(anotherBean);
. . .
}
. . .
I don't wish to reference an external properties file, as I want to keep everything local to the test.
Is there anyway I can specify in java, values for such properties, so that they will be wired in automatically to any beans which need them.
Any help would be appreciated.
Here's one simple approach:
#Configuration
public class PropertiesConfig {
#Bean
public PropertyPlaceholderConfigurer myConfigurer() {
PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
Properties props = new Properties();
Map myMap = new HashMap<String, String>();
myMap.put("my.property", "my value");
myMap.put("second.my.property", "another value");
props.putAll(myMap);
configurer.setProperties(props);
return configurer;
}
}
As of Spring Framework 4.1, you can use the #TestPropertySource annotation to declare inlined properties for the ApplicationContext loaded for your tests like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#TestPropertySource(properties = { "foo = bar", "magicNumber: 42" })
public class ExampleTests { /* ... */ }
Consult the Context configuration with test property sources section of the reference manual for details.
Prior to Spring Framework 4.1, the easiest way is to configure a custom PropertySource and register it with the Spring TestContext Framework (before the ApplicationContext is loaded for your test).
You can achieve this by implementing a custom ApplicationContextInitializer and using an org.springframework.mock.env.MockPropertySource like this:
public class PropertySourceInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.getEnvironment().getPropertySources().addFirst(
new MockPropertySource().withProperty("foo", "bar"));
}
}
You can then register your initializer for your test like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(initializers = PropertySourceInitializer.class)
public class ExampleTests { /* ... */ }
Regards,
Sam (author of the Spring TestContext Framework)
If you are using Spock you can also use #TestPropertySource:
#SpringBootTest
#TestPropertySource(properties = [ "my.test.property = bar", "..." ])
It requires the String array to be in Groovy syntax of course, caught me out. I'm using Spock 1.1
I have a Spring 3.1 #Configuration that needs a property foo to build a bean. The property is defined in defaults.properties but may be overridden by the property in overrides.properties if the application has an active override Spring profile.
Without the override, the code would look like this, and work...
#Configuration
#PropertySource("classpath:defaults.properties")
public class MyConfiguration {
#Autowired
private Environment environment;
#Bean
public Bean bean() {
...
// this.environment.getRequiredProperty("foo");
...
}
}
I would like a #PropertySource for classpath:overrides.properties contingent on #Profile("overrides"). Does anyone have any ideas on how this could be achieved? Some options I've considered are a duplicate #Configuration, but that would violate DRY, or programmatic manipulation of the ConfigurableEnvironment, but I'm not sure where the environment.getPropertySources.addFirst() call would go.
Placing the following in an XML configuration works if I inject the property directly with #Value, but not when I use Environment and the getRequiredProperty() method.
<context:property-placeholder ignore-unresolvable="true" location="classpath:defaults.properties"/>
<beans profile="overrides">
<context:property-placeholder ignore-unresolvable="true" order="0"
location="classpath:overrides.properties"/>
</beans>
Update
If you're trying to do this now, check out Spring Boot's YAML support, particularly the 'Using YAML instead of Properties' section. The profile support there would make this question moot, but there isn't #PropertySource support yet.
Add the overriding #PropertySource in a static inner class. Unfortunately, you must specify all property sources together which means creating a "default" profile as the alternative to "override".
#Configuration
public class MyConfiguration
{
#Configuration
#Profile("default")
#PropertySource("classpath:defaults.properties")
static class Defaults
{ }
#Configuration
#Profile("override")
#PropertySource({"classpath:defaults.properties", "classpath:overrides.properties"})
static class Overrides
{
// nothing needed here if you are only overriding property values
}
#Autowired
private Environment environment;
#Bean
public Bean bean() {
...
// this.environment.getRequiredProperty("foo");
...
}
}
I suggest, defining two files, where the second is optional with the profile as suffix:
#Configuration
#PropertySources({
#PropertySource("classpath:/myconfig.properties"),
#PropertySource(value = "classpath:/myconfig-${spring.profiles.active}.properties", ignoreResourceNotFound = true)
})
public class MyConfigurationFile {
#Value("${my.prop1}")
private String prop1;
#Value("${my.prop2}")
private String prop2;
}
You can do:
<context:property-placeholder location="classpath:${spring.profiles.active}.properties" />
Edit: if you need something more advanced, you can register your PropertySources on application startup.
web.xml
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.xxx.core.spring.properties.PropertySourcesApplicationContextInitializer</param-value>
</context-param>
file you create:
public class PropertySourcesApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final Logger LOGGER = LoggerFactory.getLogger(PropertySourcesApplicationContextInitializer.class);
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
LOGGER.info("Adding some additional property sources");
String[] profiles = applicationContext.getEnvironment().getActiveProfiles()
// ... Add property sources according to selected spring profile
// (note there already are some property sources registered, system properties etc)
applicationContext.getEnvironment().getPropertySources().addLast(myPropertySource);
}
}
Once you've done it you just need to add in your context:
<context:property-placeholder/>
I can't really answer to your question about multiple profiles but I guess you activate them on such an initializer, and you could register the appropriate PropertySource items during profile activations.
I can't think of any other way than one you have suggested Emerson, which is to define this bean in a separate #Configuration file with an #Profile annotation:
#Configuration
#Profile("override")
#PropertySource("classpath:override.properties")
public class OverriddenConfig {
#Autowired
private Environment environment;
#Bean
public Bean bean() {
//if..
}
}
In case you need to support multiple profiles you could do something like this:
#Configuration
public class Config {
#Configuration
#Profile("default")
#PropertySource("classpath:application.properties")
static class DefaultProperties {
}
#Configuration
#Profile("!default")
#PropertySource({"classpath:application.properties", "classpath:application-${spring.profiles.active}.properties"})
static class NonDefaultProperties {
}
}
That way you don't need to define a static configuration class for each profile.
Thanks David Harkness for putting me into the right direction.
Note: This answer provides an alternate solution to using properties files with #PropertySource. I went this route because it was too cumbersome trying to work with multiple properties files that may each have overrides while avoiding repetitive code.
Create a POJO interface for each related set of properties to define their names and types.
public interface DataSourceProperties
{
String driverClassName();
String url();
String user();
String password();
}
Implement to return the default values.
public class DefaultDataSourceProperties implements DataSourceProperties
{
public String driverClassName() { return "com.mysql.jdbc.Driver"; }
...
}
Subclass for each profile (e.g. development, production) and override any values that differ from the default. This requires a set of mutually-exclusive profiles, but you can easily add "default" as the alternative to "overrides".
#Profile("production")
#Configuration
public class ProductionDataSourceProperties extends DefaultDataSourceProperties
{
// nothing to override as defaults are for production
}
#Profile("development")
#Configuration
public class DevelopmentDataSourceProperties extends DefaultDataSourceProperties
{
public String user() { return "dev"; }
public String password() { return "dev"; }
}
Finally, autowire the properties configurations into the other configurations that need them. The advantage here is that you don't repeat any #Bean creation code.
#Configuration
public class DataSourceConfig
{
#Autowired
private DataSourceProperties properties;
#Bean
public DataSource dataSource() {
BoneCPDataSource source = new BoneCPDataSource();
source.setJdbcUrl(properties.url());
...
return source;
}
}
I am still not convinced I'll stick with this over manually configuring properties files based on the active profiles in a servlet context initializer. My thought was that doing manual configuration would not be as amenable to unit testing, but I'm not so sure now. I really prefer reading properties files to a list of property accessors.
All mentioned here solutions are a bit awkward, work only with one profile preset, and they won't work with more/other profiles. Currently a Spring team refuses to introduce this feature. But here's the working workaround I've found:
package com.example;
public class MyPropertySourceFactory implements PropertySourceFactory, SpringApplicationRunListener {
public static final Logger logger = LoggerFactory.getLogger(MyPropertySourceFactory.class);
#NonNull private static String[] activeProfiles = new String[0];
// this constructor is used for PropertySourceFactory
public MyPropertySourceFactory() {
}
// this constructor is used for SpringApplicationRunListener
public MyPropertySourceFactory(SpringApplication app, String[] params) {
}
#Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
activeProfiles = environment.getActiveProfiles();
}
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {
logger.info("Loading: {} with profiles: {}", encodedResource.toString(), activeProfiles);
// here you know all profiles and have the source Resource with main
// properties, just try to load other resoures in the same path with different
// profile names and return them as a CompositePropertySource
}
}
To make it working you have to have src/main/resources/META-INF/spring.factories with the following content:
org.springframework.boot.SpringApplicationRunListener=com.example.MyPropertySourceFactory
Now you can put your custom properties file somewhere and load it with #PropertySources:
#Configuration
#PropertySource(value = "classpath:lib.yml", factory = MyPropertySourceFactory.class)
public class PropertyLoader {
}