Different yml files for different Beans in Spring - spring

In my spring boot application, I have the main application.yml file. I have a lot of properties, and therefore I would like to have another yml files, which contains the specified properties, grouped by their logic or something.
How can I configure a Bean, to load and work all the properties from one new yml file, and another Bean from another new yml? What is the best practice for it?
I found examples using YamlPropertiesFactoryBean, and this bean can read several resources (yml files), but in an another Bean, when I autowire this YamlPropertiesFactoryBean, I cannot get that specific yml, because the getObject() of this YamlPropertiesFactoryBean will have all the yml resources I added to it.

Finally I have it! This is how it works:
I have a properties configuration class, which loads the yml files:
#Configuration
public class PropertiesConfig {
public static final String PERSONS_FILE_NAME = "persons.yml";
public static final String FOODS_FILE_NAME = "foods.yml";
#Bean
public PropertySourcesPlaceholderConfigurer properties() {
final PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
final YamlPropertiesFactoryBean personsYaml = personsPropertiesFromYamlFile();
final YamlPropertiesFactoryBean foodsYaml = foodsPropertiesFromYamlFile();
propertySourcesPlaceholderConfigurer.setPropertiesArray(personsYaml.getObject(), foodsYaml.getObject());
return propertySourcesPlaceholderConfigurer;
}
#Bean
#Qualifier(PersonsManager.QUALIFIER_NAME)
public YamlPropertiesFactoryBean personsPropertiesFromYamlFile() {
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource(PERSONS_FILE_NAME));
return yaml;
}
#Bean
#Qualifier(FoodsManager.QUALIFIER_NAME)
public YamlPropertiesFactoryBean foodsPropertiesFromYamlFile() {
final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource(FOODS_FILE_NAME));
return yaml;
}
}
And finally, I have two beans (managers), which hold only the corresponding yml properties:
#Component
public class PersonsManager extends YmlPropertiesManager {
public static final String QUALIFIER_NAME = "personsYaml";
#Autowired
public PersonsManager(#Qualifier(QUALIFIER_NAME) YamlPropertiesFactoryBean yamlObject) {
super(yamlObject);
}
...
}
and:
#Component
public class FoodsManager extends YmlPropertiesManager {
public static final String QUALIFIER_NAME = "personsYaml";
#Autowired
public FoodsManager(#Qualifier(QUALIFIER_NAME) YamlPropertiesFactoryBean yamlObject) {
super(yamlObject);
}
...
}
So the main thing here is the #Qualifier annotation.

Beans shouldn't be aware of yaml files. The yaml files are just sources that use used to build up the Spring Environment instance.
If you want specific properties for specific beans, the best way is to prefix those properties in application.yaml, and then use the #ConfigurationProperties with an argument of the prefix you want to use, to bind those properties to the bean in question.
See here:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

Related

#Value not getting injected in other packages

I have a property source defined in Spring where I am trying to load a properties file present in the classpath
#Configuration
#PropertySource(name = "props", value = "classpath:prod.properties")
public class PropertyPlaceholderConfigurerConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[]
{ new ClassPathResource("prod.properties") };
ppc.setLocations(resources);
ppc.setIgnoreUnresolvablePlaceholders(true);
return ppc;
}
#Value("${DATABASE_NAME}") private String DATABASE_NAME;
#Bean
public String test() {
System.out.println(DATABASE_NAME);
}
}
My prod.properties file has 1 entry
DATABASE_NAME=proddb_123
Now in the test() bean prints as proddb_123
Two problems I am facing is
I have another class in a different package where I am trying to
inject it
#Value("${DATABASE_NAME}") private String DATABASE_NAME;
But the value of this is always "${DATABASE_NAME}"
I have different gradle projects like webapplication-gradle is one
where the #PropertySouce configuration is defined and another
project like storage-gradle. Can I inject the DATABASE_NAME in any
class present in storage-gradle project.
In the other classes where you would like to inject the #Value(${DATABASE_NAME}") you have to also yse the #PropertySource annotation. Another option is to remove the #PropertySource annotations and in the applicaiton-context.xml defined a bean of class org.springframework.beans.factory.condig.PropertyPlaceholderConfigurer and for the property locations list add a value for the classpath:prod.properties

Spring #PropertySource using YAML

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
)

Can a PropertiesFactoryBean read a value from application.yml?

My project has a dependency that requires a set a properties object that can be read by #Value annotations:
#Value("#{myProps['property.1']}")
To do this in JavaConfig, I'm using the following:
#Bean(name="myProps")
public static PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("myprops.properties"));
return bean;
}
This works as expected. My properties look as follows:
property.1=http://localhost/foo/bar
property.2=http://localhost/bar/baz
property.3=http://localhost/foo/baz
I'm using Spring Boot for this project, so I'd love to be able to do something like the following:
myprops.properties:
property.1=${base.url}/foo/bar
property.2=${base.url}/bar/baz
property.3=${base.url}/foo/baz
Then I could configure the base.url based on different profiles.
application.yml:
base:
url: http://localhost
---
spring:
profiles: staging
base:
url: http://staging
---
spring:
profiles: production
base:
url: http://production
I've tried to do this and it doesn't work. As a workaround, I've created three different .properties files (myprops.properties, myprops-staging.properties, etc.) and loaded them with three different #Configuration classes. This works, but seems cumbersome.
#Configuration
public class DefaultConfiguration {
#Bean(name="myProps")
public static PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("myprops.properties"));
return bean;
}
}
#Configuration
#Profile("staging")
public class StagingConfiguration {
#Bean(name="myProps")
public static PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("myprops-staging.properties"));
return bean;
}
}
#Configuration
#Profile("production")
public class ProductionConfiguration {
#Bean(name="myProps")
public static PropertiesFactoryBean mapper() {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("myprops-production.properties"));
return bean;
}
}
Is it possible to configure my PropertiesFactoryBean to read values from application.yml? If not, is there an easier way to configure properties with JavaConfig?
I ended up doing this programmatically and it gives me the behavior I was looking for:
#Value("${base.url}")
private String baseUrl;
#Bean(name = "myProps")
public PropertiesFactoryBean mapper() throws IOException {
PropertiesFactoryBean bean = new PropertiesFactoryBean();
bean.setLocation(new ClassPathResource("myprops.properties"));
bean.afterPropertiesSet();
// replace ${base.url} in values
Properties props = bean.getObject();
Enumeration names = props.propertyNames();
while (names.hasMoreElements()) {
String name = names.nextElement().toString();
String value = props.getProperty(name);
if (value.contains("${base.url}")) {
props.setProperty(name, value.replace("${base.url}", baseUrl));
}
}
bean.setLocalOverride(true);
bean.setProperties(props);
bean.afterPropertiesSet();
if (log.isDebugEnabled()) {
log.debug("Base URL: " + baseUrl);
}
return bean;
}
I'm not 100% sure what you are really looking do there, and you don't really say what doesn't work, but it looks like you want to mix YAML and properties formats for your external config? Why not just use "application.yml"? If I were doing something like that, and I needed to use a properties file for some reason as well, then I would use #PropertySource on one of my SpringApplication source files (that way the placeholders should be replaced when the values are resolved I think).

How do I set #Qualifier without XML in Spring 3+

I'm using the below configuration setup. The #configuration class loads the property file, and then there is an arraylist that produced which extracts the relevant chunks of the property file in a way that the classes that depend on barUserList and fooUserList can consume easily. They don't even know that it came from a property file. Huzzah for DI!
My problem comes when I try to tell Spring which one of these I want. Class Foo wants fooUserList so I should be able to use the #Qualifier annotation, but I can't find a way to /set/ the qualifier outside of XML.
So my question is this, how do I set the Qualifier for these two Spring beans in Javaland? Zero XML config is a big goal for me. I know that you can set #name and the #qualifier mechanism for Spring will default to the #name, but I'd like to avoid using that. I don't like things that "default to" other things.
I'm using Spring 3.2.5.RELEASE
#Configuration
public class AppConfig {
#Bean
Properties loadProperties() throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream("checker.properties"));
return properties;
}
#Bean
#Autowired
ArrayList<String> barUserList(Properties properties) {
ArrayList<String> barUsernames = new ArrayList<String>();
Collections.addAll(barUsernames, properties.getProperty("site.bar.watchedUsernames", "").split(","));
return barUsernames;
}
#Bean
#Autowired
ArrayList<String> fooUserList(Properties properties) {
ArrayList<String> fooUsernames = new ArrayList<String>();
Collections.addAll(fooUsernames, properties.getProperty("site.foo.watchedUsernames", "").split(","));
return fooUsernames;
}
}
One way could be by defining a name for the #Bean and using it on #Qualifier as follows:
#Bean(name="barUserList")
#Autowired
ArrayList<String> barUserList(Properties properties) {
ArrayList<String> barUsernames = new ArrayList<String>();
Collections.addAll(barUsernames, properties.getProperty("site.bar.watchedUsernames", "").split(","));
return barUsernames;
}
and within the use you could have something like:
// ...
#Autowired
#Qualifier("barUserList")
private List<String> userList;
// ...

Can #PropertySources be chosen by Spring profile?

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 {
}

Resources