Springboot - injection from application.yml depending method name - java

I refered Spring Boot - inject map from application.yml for injecting map from application.yml file
My application.yml snippet is below
easy.app.pairMap:
test1: 'value1'
test2: 'value2'
Properties file is like below
#Component
#Configuration
#ConfigurationProperties("easy.app")
#EnableConfigurationProperties
public class TestProperties {
private Map<String, String> pairMap= new HashMap<String, String>();
public void setPairMap(Map<String, String> pairMap) {
this.pairMap= pairMap;
}
}
But , I found that the value injection happens only when setters and getters are in proper format.ie getPairMap and setPairMap. Not when using getPairs or SetPairs. What is the reason for this behaviour

Spring takes your property full name easy.app.pairMap find ConfigurationProperties by prefix easy.app and then it try to find setter with name setPairMap, it takes property name pairMap and "converts" it to setPairMap.
If you create method setPairs property name should be like easy.app.pairs.

To bind to properties by using Spring Boot’s Binder utilities (which is what #ConfigurationProperties does), you need to have a property in the target bean and you either need to provide a setter or initialize it with a mutable value.
How does Spring can understand that it needs to use SetPairs method to set your pairMap property? There is convention for naming of getters and setters and you should follow this convention if you want everything to work.

Related

Is it known you shouldn't name methods starting with 'get' when using #ConfigurationProperties on a Spring #Component

Suppose a simple Spring Boot #Component like this one:
#Component
#Data
#EnableScheduling
#ConfigurationProperties(prefix = "demo")
public class DemoClass {
private String aString;
private Long aLong;
#Scheduled(fixedDelayString = "${demo.delay}")
void getSomething() {
System.out.println("aString = " + aString);
System.out.println("aLong = " + aLong.toString());
}
}
It will not start throwing
ConfigurationPropertiesBindException: Error creating bean with name 'demoClass': Could not bind properties to 'DemoClass'
All you need to fix is a getSomething method name. Just rename it to putSomething for example.
I've lost three hours debugging Spring Boot sources and found it: Spring tries to bind Bean property named Something. And the exception occurs.
I know it's a weird practice to name methods starting with get if it's not a getter, but is it mentioned somewhere in Spring Docs? Does it say something about guessing properties names from method names?
Yes Spring uses the JavaBeans standard to process the configuration properties POJO, as explained here:
Such arrangement relies on a default empty constructor and getters and setters are usually mandatory, since binding is through standard Java Beans property descriptors, just like in Spring MVC.

Requirements for properties being resolved with #ConfigurationProperties

I am using Azure KeyVault in Spring Boot to resolve secrets. The vault has a limitation in that properties can only be separated by -. I am using azure-keyvault-secrets-spring-boot-starter and this dependency replaces dashes with dots to be able to store secrets such as spring-datasource-url.
In my project we are using a fairly complex KeyVault and this requires us to prefix properties to know who owns them. So I store my property as prefix-sampleProperty in the vault. The vault starter lets my use this property on two different ways:
#Value("${prefix.sampleProperty}"
#Value("${prefix-sampleProperty}"
However, since my part of the application is only interested in a single namespace within this vault (the prefix namespace), I want to use the Spring annotation #ConfigurationProperties(prefix = "prefix") and simply disregard writing it for each property:
#Value("${sampleProperty}"
However, this does not work and fails with the following error:
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'sampleProperty' in value "${sampleProperty}"
I have verified that my environment variables in Spring contain the property, and it exists in two forms, both with . (dot) and - (dash). They exist under propertySource -> source -> propertySources -> propertySourcesList -> KeyVaultPropertySource when Spring attempts to resolve the value from here.
All available property sources that Spring iterates through:
When Spring calls propertySource.getProperty(key);, key is sampleProperty, which does not exist, only prefix-sampleProperty and prefix.sampleProperty do.
This in turn calls this method, and here secretName is also sampleProperty, which does not exist in the map in this class.
So, my questions:
Are there any specific requirements for resolving properties with class level annotation ConfigurationProperties other than it has to be separated by .?
Are there any specific requirements for how to add properties to Springs environment to be able to resolve them with ConfigurationProperties?
Is this a fault in the implementation of the Azure KeyVault property source?
Edit:
#Getter
#Component
// #EnableConfigurationProperties // also tried here, not working
#ConfigurationProperties(prefix = "prefix")
public class SampleConfiguration {
private String sampleProperty;
}
I also added #EnableConfigurationProperties at the same place where I added #SpringBootApplication. This is how I wire the configuration:
#Configuration
// #EnableConfigurationProperties(DataLakeConfiguration.class) // also tried, no difference. also removed autowired
public class AzureBeanConfiguration {
#Autowired
public AzureBeanConfiguration(final SampleConfiguration configuration) {
this.configuration = configuration;
}
#Bean
public ADLStoreClient getDataLakeClient() {
// All properties on configuration object is null here
}
}
If I instead use this, it works:
#Getter
#Configuration
public class SampleConfiguration {
#Value("${prefix.sampleProperty}") // or prefix-sampleProperty
private String sampleProperty;
}
Edit 2:
Config class is annotated with:
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "prefix")
public class SampleConfiguration {
private String sampleProperty;
}
I set a breakpoint here, and when I hit it the parameter name equals prefix. I never receive anything like prefix.sampleProperty or anything containing that key, nothing resembling the name sampleProperty.

How to inject Maps using Spring #Value

We are using Spring Boot 2.
We want to use the refresh mechanism of Spring Boot, but due to a bug we can't use #Configuration, hence we are forced to replace all theses by #Value in combination with #RefreshScope.
So we used that:
#Configuration
#RefreshScope
public class MyConfig {
#Value("${myMap}")
private Map<String, String> myMap;
}
With that YAML file for example:
myMaps:
key1: Value
key2: Another Value
But we get an error out of it:
Error creating bean with name 'scopedTarget.myMap': Unsatisfied dependency expressed through field 'mapMap'; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'java.util.Map'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map': no matching editors or conversion strategy found
We found already other thread with a similar topic:
How to fill HashMap from java property file with Spring #Value
How to inject a Map using the #Value Spring Annotation?
But we can't use other kind of property bindings, as we are deploying this app into kubernetes and use the config maps of kubernetes.
So we wonder, if there is any other chance to get #Value working together with Map
Instead of: #Value("${myMap}"), you need to write like this #Value("#{${myMap}}").
'#' treat whats in the curly bracket after it as Spring Expression Language(SpEL).
I don't think Kubernetes should prevent you from using #ConfigurationProperties (example here) with #RefreshScope. (Or is something else blocking you from using this?)
You could set up a configmap containing your properties file and mount that as a volume.
How you trigger a refresh is another question (if you need hot loading instead of changing your configmap together with your app). One option is to use spring-cloud-kubernetes which uses a similar approach and has multiple reload-triggering options. I'd expect you could use #ConfigurationProperties just like in the example for that project. Alternatively, it's also possible to watch for changes but that would require introducing more code.
I found something, that might solve my problem, but perhaps there is a better solution than this:
Based on org.springframework.cloud.logging.LoggingRebinder.setLogLevels(LoggingSystem, Environment)
private static final Bindable<Map<String, String>> STRING_STRING_MAP = Bindable
.mapOf(String.class, String.class);
protected void setLogLevels(LoggingSystem system, Environment environment) {
Map<String, String> levels = Binder.get(environment)
.bind("logging.level", STRING_STRING_MAP).orElseGet(Collections::emptyMap);
for (Entry<String, String> entry : levels.entrySet()) {
setLogLevel(system, environment, entry.getKey(), entry.getValue().toString());
}
}

What's the Spring way of accessing properties from an entity?

I'm new to Spring and I'm building an application where some entities (JPA/Hibernate) need access to a property from application.properties. I do have a configuration class in which this is trivial:
#Configuration
public class FactoryBeanAppConfig {
#Value("${aws.accessKeyId}")
private String awsAccessKeyId;
#Value("${aws.secretKey}")
private String awsSecretKey;
}
but since entities do not have and I think they should not have the annotations such as #Configuration or #Component, what's the Spring way for them to access the property?
Now, I know I can create my own class, my own bean, and make it as a simple wrapper around the properties; but is that the Spring way to do it or is there another way?
specify Property file location using #PropertySource
Something like below
#PropertySource("classpath:/application.proerties")
You also need to add below bean in your config
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigIn() {
return new PropertySourcesPlaceholderConfigurer();
}
There is no "Spring way", since JPA entities and Spring have nothing to do with each other. Most importantly, JPA entities are not Spring beans, so Spring doesn't know or care about them as they're not managed by Spring.
You can try to hack around, trying in vain to access Spring's configuration from code that should not be trying to access it, or you can accept the truth that your design is broken and you're trying to do something that's not meant to be done.
As was proposed several times, use a service class for this. It's managed by Spring, so it can access the Spring config, and it can handle entities, so there's no crossing boundaries.
First create a public static variable in some bean managed by Spring, then make the following use of the #Value annotation.
public static String variable;
#Value("${variable}")
private void setVariable(String value) {
variable = value;
}
It will be set at runtime on startup, now you can access it from entities and everywhere else because it is just a public static var.
You can use #PropertySource to load the properties file as follows
#Configuration
#PropertySource("classpath:/com/organization/config/application.proerties")
public class FactoryBeanAppConfig {
...
}
Entities should not acces environment properties. If you are using your entity through a service, then the service can access the properties to act on the entity.

SpringCloudConfig When I use these annotations #RefreshScope #ConfigurationProperties ,I was in trouble

SpringCloudConfig trouble
When I use these annotations #RefreshScope #ConfigurationProperties, I was in trouble.
#Component
#RefreshScope
#ConfigurationProperties(prefix = "config.message")
public class MessageProperties {
private int max_num;
private boolean begin;
private String ding_department;
// getter, setter...
}
like this ! Config does not work;
but when I use only #ConfigurationProperties,it works. So What's the use of #RefreshScope. And how to fix it?
So when you use "I was in trouble" in stackoveflow you are in trouble that there is high probability no one to answer.
#ConfigurationProperties is used for mapping properties to a POJO, with prefix you start using hierarchical properties structure. SO for example based on your description your code will work if you have the following .yml
config:
message:
max_num:
begin:
ding_department:
If for example you use spring-cloud-config server to store the configuration properties and spring-boot and want on change on the file the corresponding Bean with injected conf file to be update you add #RefreshScope, but even if you do this the bean is not updated you have to call the /refresh url or to trigger event which will refresh it.

Categories