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

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;
// ...

Related

Getting qualifier names from initialized bean objects

I have two beans of the same type;
#Bean
public RestTemplate jsonTemplate() {
return new RestTemplate();
}
#Bean
public RestTemplate xmlTemplate() {
return new RestTemplate();
}
And I autowire both beans into a list as follows;
#Autowired
private List<RestTemplate> templates;
The list templates will have both beans inside with size=2.
From this list, how can I get their names (["jsonTemplate", "xmlTemplate"])?
It was really simple...
Just doing;
#Autowired
private Map<String, RestTemplate> templates;
will let Spring to insert the names as keys and the beans themselves as the values in
the map.
It seems Spring just stops keeping track of the naming after the injection. So I don't know if there is any other way (or, if even possible, simpler way) than this?
You could use map of beans:
#Bean
public Map<String, RestTemplate> templateMap(RestTemplate jsonTemplate, RestTemplate xmlTemplate) {
Map<String, RestTemplate> map = new HashgMap<>();
map.put("jsonTemplate", jsonTemplate);
map.put("xmlTemplate", xmlTemplate);
return map;
}
#Autowired
private Map<String, RestTemplate> templates;

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

How can I build a database-based Spring Boot environment / property source?

The goal is running Spring Boot application with an Environment containing keys & values loaded and generated by a database connection (DataSource).
Or, more abstract defined: While a configuration by files only should be preferred (faster, easier, more tolerant, ...), sometimes you will find use cases where a non-static files based configuration is required.
Spring 3.1 introduces Environment which is actually a property resolver (extends PropertyResolver) and is based on a list of objects PropertySource. Such a source is a wrapper/adapter for a properties (file or object), a map or something else. It really looks like this is the way how to get.
Properties properties = new Properties();
properties.put("mykey", "in-config");
PropertiesPropertySource propertySource = new PropertiesPropertySource("myProperties", properties);
However, this cannot be done in #Configuration classes since it must be available for the configuration phase. Think about something like
#Bean public MyService myService() {
if ("one".equals(env.getProperty("key")) {
return new OneService();
} else {
return new AnotherService();
}
}
// alternatively via
#Value("${key}")
private String serviceKey;
Additionally, the more recent Spring releases support Condition as well.
With a OneCondition like
public class OneCondition implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
return "one".equals(context.getEnvironment().getProperty("key"));
}
}
This can be used like
#Bean
#Conditional(OneCondition.class)
public MyService myService() {
return new OneService();
}
My non working ideas:
Option 1: #PropertySource
The corresponding annotation processor handles files only. This is fine, but not for this use case.
Option 2: PropertySourcesPlaceholderConfigurer
An example with a custom property source would be
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() throws IOException {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
pspc.setIgnoreUnresolvablePlaceholders(Boolean.TRUE);
// create a custom property source and apply into pspc
MutablePropertySources propertySources = new MutablePropertySources();
Properties properties = new Properties();
properties.put("key", "myvalue");
final PropertiesPropertySource propertySource = new PropertiesPropertySource("pspc", properties);
propertySources.addFirst(propertySource);
pspc.setPropertySources(propertySources);
pspc.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:application.properties"));
return pspc;
}
However, this only configures the placeholders (i.e. #Value. Any environment.getProperty() will not profit.
This is more or less the same as Option 1 (less magic, more options).
Do you know a better option? Ideally, the solution would use the context datasource. However, this is conceptually an issue since the datasource bean creation relies on properties itself...
Spring Boot provides some different extensions point for this early processing step: http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the-environment-or-application-context
Internally, these options are realised with implementations of standard Spring ApplicationContextInitializer.
Depending on the priority of the source, the key/value will be available both in environment.getProperty() as well in property placeholders.
Because these is a pre-config-context listeners, no other beans are available, like a DataSource. So if the properties should be read from a database, the datasource and connection have to be build manually (eventually a separated datasource connection lookup).
Option: ApplicationListener for ApplicationEnvironmentPreparedEvent
Build an implementation of an application listener consuming ApplicationEnvironmentPreparedEvents and
register it in META-INF/spring.factories and the key org.springframework.context.ApplicationListener
- or -
use the SpringApplicationBuilder:
new SpringApplicationBuilder(App.class)
.listeners(new MyListener())
.run(args);
Example
public class MyListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
final ConfigurableEnvironment env = event.getEnvironment();
final Properties props = loadPropertiesFromDatabaseOrSo();
final PropertiesPropertySource source = new PropertiesPropertySource("myProps", props);
environment.getPropertySources().addFirst(source);
}
}
Reference: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html#boot-features-application-events-and-listeners
Option: SpringApplicationRunListener
Besides the special event, there is also a more general event listener containing hooks for several types of events.
Build an implementation of SpringApplicationRunListener and register it in META-INF/spring.factories and the key org.springframework.boot.SpringApplicationRunListener.
Example
public class MyAppRunListener implements SpringApplicationRunListener {
// this constructor is required!
public MyAppRunListener(SpringApplication application, String... args) {}
#Override
public void environmentPrepared(final ConfigurableEnvironment environment) {
MutablePropertySources propertySources = environment.getPropertySources();
Properties props = loadPropertiesFromDatabaseOrSo();
PropertiesPropertySource propertySource = new PropertiesPropertySource("myProps", props);
propertySources.addFirst(propertySource);
}
// and some empty method stubs of the interface…
}
Reference: http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-boot-application.html#howto-customize-the-environment-or-application-context
Option: ApplicationContextInitializer
This is an old friend for all "non Boot" Spring developers. However, SpringApplication mocks a configuration away -- at first.
Build an implementation of ApplicationContextInitializer and
register it in META-INF/spring.factories and the key org.springframework.context.ApplicationContextInitializer.
- or -
use the SpringApplicationBuilder:
new SpringApplicationBuilder(App.class)
.initializers(new MyContextInitializer())
.run(args);
Example
public class MyContextInitializer implements ApplicationContextInitializer {
#Override
public void initialize(final ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
Properties props = loadPropertiesFromDatabaseOrSo();
PropertiesPropertySource propertySource = new PropertiesPropertySource("myProps", props);
propertySources.addFirst(propertySource);
}
}

#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

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