Spring configuration from yaml - combining #ConfigurationEnabled loading for parametrized file name - spring

I'm having a problem probabably quite unique. I have two applications in the same project, using two different spring configuration files.
When I run them I manually load the context
context = new ClassPathXmlApplicationContext("hal.context.xml");
context.registerShutdownHook();
and I specify the required spring context file in each of them.
The problems comes from the configuration, because I have two configuration files stored in yaml files.
My best solution would be to use the Spring Boot facility to load the configuration (they have the same structure) in a POJO using
#org.springframework.context.annotation.Configuration("configuration")
#ConfigurationProperties
#EnableConfigurationProperties
and
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("filename"));
propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
return propertySourcesPlaceholderConfigurer;
}
I cannot find a way to load the configuration in a parametrised way... Is there any option combining this and perhaps
<bean id="yamlProperties" class="org.springframework.beans.factory.config.YamlPropertiesFactoryBean">
<property name="resources" value="classpath:hal.config.yml"/>
</bean>
but instead of using as property placeholder <context:property-placeholder properties-ref="yamlProperties"/> to find a way to automatically generate the POJO with the configuration.
Thank you in advance
Luca

You could separate the configuration values by using two different prefixes (one for each config file):
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setPlaceholderPrefix("${app1:");
propertySourcesPlaceholderConfigurer.setPlaceholderSuffix("}");
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("app1_config_filename"));
propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
return propertySourcesPlaceholderConfigurer;
}
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setPlaceholderPrefix("${app2:");
propertySourcesPlaceholderConfigurer.setPlaceholderSuffix("}");
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("app2_config_filename"));
propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
return propertySourcesPlaceholderConfigurer;
}
Then when injecting configuration values into your beans you can do:
#Value("{$app1:myVal}")
String myVal;
and
#Value("{$app2:myVal}")
String myVal;
Into beans from app1 and app2
Hope this helps

Related

PropertySourcesPlaceholderConfigurer with highest priority

in my Spring Boot project I have the default application.properties in resources directory, I also configured Spring to load a properties file from file system (in working directory), called config.properties. some properties are redefined in the config.properties file, I want them have higher priority and overwrite properties defined in application.properties.
here is the PropertySourcesPlaceholderConfigurer bean :
#Configuration
public class Config {
#Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
PropertySourcesPlaceholderConfigurer properties =
new PropertySourcesPlaceholderConfigurer();
properties.setLocation(new FileSystemResource("config.properties"));
properties.setIgnoreResourceNotFound(false);
properties.setOrder(Ordered.HIGHEST_PRECEDENCE);
return properties;
}
}
but it does not work. the values in application.properties are applied, not config.properties. I tried to change setOrder(Ordered.HIGHEST_PRECEDENCE); to setOrder(Ordered.LOWEST_PRECEDENCE); nothing happened.
You can try call the method PropertySourcesPlaceholderConfigurer.setLocalOverride(boolean localOverride)
properties.setLocalOverride(true)
See for details: https://docs.spring.io/spring-framework/docs/4.1.6.RELEASE_to_4.2.0.RC1/Spring%20Framework%204.1.6.RELEASE/org/springframework/core/io/support/PropertiesLoaderSupport.html#setLocalOverride-boolean-

Jasypt Spring EncryptablePropertyPlaceholderConfigurer not reading VM arguments

Ever since I replaced Spring's PropertySourcesPlaceholderConfigurer with Jasypt's EncryptablePropertyPlaceholderConfigurer, I've not been able to read the VM arguments passed to the application (eg: -DtestVar=testVal) through #Value annotations. The properties from app.properties file is being read and/or decrypted as expected. But the VM arguments are just not read. It is supposed to override the properties on app.properties with the value passed as VM argument.
What the config used to be (when VM arguments were being read and working as expected):
#Bean
public static PropertySourcesPlaceholderConfigurer myConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
My current Jasypt EncryptablePropertyPlaceholderConfigurer config looks like this, which replaced the above:
#Bean
public static PooledPBEStringEncryptor configuEncryptor() {
PooledPBEStringEncryptor configuEncryptor = new PooledPBEStringEncryptor();
configuEncryptor.setConfig(environmentVariableConfig());
return configuEncryptor;
}
#Bean
public static EnvironmentStringPBEConfig environmentVariableConfig() {
final EnvironmentStringPBEConfig config = new EnvironmentStringPBEConfig();
config.setAlgorithm(ALGORITHM);
config.setPassword(PASSWORD);
return config;
}
#Bean
public static EncryptablePropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
EncryptablePropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new EncryptablePropertyPlaceholderConfigurer(configuEncryptor());
propertyPlaceholderConfigurer.setLocations(
new ClassPathResource("app.properties")
);
propertyPlaceholderConfigurer.setIgnoreResourceNotFound(true);
propertyPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
return propertyPlaceholderConfigurer;
}
There were no issues reading externally passed VM arguments(which overrides props in app.properties) when Spring's PropertySourcesPlaceholderConfigurer was being used. This has become an issue after implementing Jasypt's EncryptablePropertyPlaceholderConfigurer. Have anyone been able to solve this?

about spring boot Profile can not work

when I use command
mvn spring-boot:run -Dspring.profiles.active=web
my project is running,but #Profile("web") bean code not used,that only use
properties which the bean write by
#Profile("default")
how can I change for it,and the properties change to web profile?
#Profile("default")
#Bean
static public PropertySourcesPlaceholderConfigurer defaultPropertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer p = new PropertySourcesPlaceholderConfigurer();
Resource[] resourceLocations = new Resource[] { new ClassPathResource("job.core.properties") };
p.setLocations(resourceLocations);
return p;
}
#Profile("web")
#Bean
static public PropertySourcesPlaceholderConfigurer prodWebPropertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer p = new PropertySourcesPlaceholderConfigurer();
Resource[] resourceLocations = new Resource[] {new ClassPathResource("job.core.ris.properties") };
p.setLocations(resourceLocations);
return p;
}
job.core.ris.properties
db.driverClass=com.mysql.jdbc.Driver
db.jdbcUrl=jdbc:mysql://192.168.0.68:3306/job_ris?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8
db.user=root
db.password=
job.core.properties
db.driverClass=com.mysql.jdbc.Driver
db.jdbcUrl=jdbc:mysql://192.168.0.68:3306/dev?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8
when I use action then,show this
Work with the framework not against/around it. Spring Boot has build in support to load profile specific application.properties files.
Instead of trying to shoehorn multiple PropertyPlaceholderConfigurer into a Spring Boot application. Create an application.properties and application-web.properties containing your properties.
application.properties
db.driverClass=com.mysql.jdbc.Driver
db.jdbcUrl=jdbc:mysql://192.168.0.68:3306/dev?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8
application-web.properties
db.jdbcUrl=jdbc:mysql://192.168.0.68:3306/job_ris?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8
db.user=root
db.password=
(notice the missing db.driverClass you only need to include the different properties).
Next remove your custom #Bean annotated methods and let Spring Boot do the heavy lifting.
Pro Tip: Judging from the names of the properties you also have a custom #Bean for your DataSource. Instead of using custom names you probably want to use the spring.datasource.* properties and let Spring Boot create/manage your datasource.

Different yml files for different Beans in 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

Why does the ConfigurationPropertiesBindingPostProcessor considers only one PropertySourcesPlaceholderConfigurer?

The ConfigurationPropertiesBindingPostProcessor is used whenever a bean is annotated with #ConfigurationProperties in order to bind property values to a bean.
The implementation of ConfigurationPropertiesBindingPostProcessor (Spring Boot version 1.3.1) determines its property sources in the method deducePropertySources() as shown below:
private PropertySources deducePropertySources() {
PropertySourcesPlaceholderConfigurer configurer = getSinglePropertySourcesPlaceholderConfigurer();
if (configurer != null) {
// Flatten the sources into a single list so they can be iterated
return new FlatPropertySources(configurer.getAppliedPropertySources());
}
...
}
When looking into getSinglePropertySourcesPlaceholderConfigurer() you'll discover that only the first PropertySourcesPlaceholderConfigurer is taken and the others are ignored.
What is the reason for this?
In my Spring configuration I have multiple PropertySourcesPlaceholderConfigurer beans and I would expect that all of them are used when binding the property values.
Of course I can create a workaround by defining an interface MyPropertySourceResources that looks like this:
public interface MyPropertySourceResources {
List<Resource> getResources();
}
and in my Spring configuration I define only one PropertySourcesPlaceholderConfigurer that takes a list of MyResourceLocartion as parameter:
#Bean
public static PropertySourcesPlaceholderConfigurer gdaPropertySourcesPlaceholderConfigurer(
List<MyPropertySourceResources> propertySourceResources) {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
List<Resource> allResources = propertySourceResources.stream()
.flatMap(p -> p.getResources().stream())
.collect(Collectors.toList());
propertySourcesPlaceholderConfigurer.setLocations(allResources.toArray(new Resource[allResources.size()]));
return propertySourcesPlaceholderConfigurer;
}
#Bean
public MyPropertySourceResources gdaPropertySourceResources() {
return () -> Arrays.asList(new FileSystemResource(new File("path/to/my.properties")));
}
In this solution each module defines its own MyPropertySourceResources bean and all of them will be used to build up the only PropertySourcesPlaceholderConfigurer.
But back to my question: Is there a good reason why the ConfigurationPropertiesBindingPostProcessor takes only one PropertySourcesPlaceholderConfigurer into account?

Resources