#Value injection of field not working in case of an empty Map - spring

I am trying to inject a map property inside my spring-component class using #Value annotation.
My class is something like this
#Service
class SomeService {
#Value("#{\${some.map.property}}")
lateinit var map: Map<String, Boolean>
}
If my application.properties is as -
some.map.property={a:true}
The injection works fine. But if the value is empty i.e. something like this
some.map.property={}
The application throws an error lateinit property map has not been initialised. Is there a way in spring to initialise the map to an empty map if the value of the property is {}.I have a condition that this property will always be there and only the value of the property can change. I am using spring boot version -> 2.7.1 and the app is built using Kotlin.
If the property is set to {}, then I am getting property uninitialised error. If the value is present it works fine.
Given I always have to keep the property and can only change the value. Is there a way to initialise the property with an empty map if the value is {}.
I can always read the property as string and do a conversion to Map. But I want to avoid that.
Thanks!

So the problem with your code is that whenever you pass:
some.map.property={}
You will receive:
kotlin.UninitializedPropertyAccessException: lateinit property map has not been initialized
Because {} is mapped to null, but you are expecting to have Map<String, Boolean> that's why you have a problem.
The solution is simple, you can use the default value if null occurs:
#Value("#{\${some.map.property}}")
val map: Map<String, Boolean> = emptyMap()
In that case for {} value in your property it will be mapped to empty map.

Related

Fail to initialize a property with default value of #Value annotation in Kotlin

I have a property that holds Redis port in my Redis config class.
#Value("\${redis.port:63799}")
lateinit var port: Integer
init {
this.redisServer = RedisServer(port.toInt())
}
When I run my code I get this error:
kotlin.UninitializedPropertyAccessException: lateinit property port has not been initialized
#Value annotation is processed after the object is constructed.
The init block is part of object construction (it's common for all your constructors).
In other words, you're trying to reference the #Value injected property too early.
One way to work around it is to delay RedisServer construction in some way, i.e.:
Use lazyinit for the RedisServer.
Use #PostConstruct on a method to initialise the RedisServer.

Kotlin init not picking up value from #Value

I want to initialise a member variable from a value I pick from ENV but it is not available in init block as it gets picked up after object initialisation
private lateinit var needValueHere: String
#Value("\${CLIENT_ID:NA}")
private val CLIENT_ID: String = ""
init {
this.needValueHere = this.CLIENT_ID
}
This is a simplified version of the actual problem.
I have verified the value is available in the member functions.
Your object is constructing by the following way:
Create object (e.g. call constructor)
Via reflection: put dependencies (e.g. fill values under #Autowired, #Value and other annotations).
Your init block is part of constructor, e.g. all Spring-related items aren't initialized here.
How you can fix this:
Extract properties to the type-safe configuration (please see official docs here)
Use notation of class like below.
Create private lateinit var field and don't call it until Spring initialization finishing (this is useful for integration tests, e.g. test methods start only after full warmup). Another option - use kotlin lazy notation. However whole this item couldn't be named as "good code".
class MyService(#Value("\${CLIENT_ID:NA}") private val needValueHere: String) {
/* */
}

#autowire beans and #value properties after object mapper deserialized json

I am using spring framework.
I am using objectMapper to desiriale store.json file:
service:
objectMapper.readValue(new File(jsonFilePath), Store.class)
store.json:
{
"type": "Store",
"name": "myStore",
}
Store.class:
#Value("${store.size:1000}")
private Integer sroreSize;
#autowire
private storePersistency storePersistency;
public Store(#JsonProperty("name") String name) {
super(name);
}
I am trying find out how to #autowire beans and #value properties in store.class, beans and properties that exist in applicationContext.
In current example sroreSize and storePersistency still null.
I know that I can inject fields to object mapper and then use #JacksonInject annotation but I have a lot of field to inject - not a good option for me.
Custom desirializer also not a good option for me.
Is there any way not to use custom desirializer or not to inject every bean/property that I need in store.class?
Something that injects all the beans and properties and I simply can use it in Store.class.
So you want some Store fields like storePersistency and sroreSize to be initialized once at application startup (which is when Spring will setup the application context) and then at runtime create multiple different Store objects differing in some fields as name that are initialized by Jackson.
I suggest annotating Store with #Component to get Spring to initialize #Value and #Autowired fields. The #Scope annotation will cause a new independent Store instance to be created each time. Simplified example:
#Component
#Scope(SCOPE_PROTOTYPE)
class Store {
private String name;
#Value("${store.size:1000}")
private Integer sroreSize;
}
Then the key is method readerForUpdating where you can pass an existing instance of Store and Jackson will update that instead of creating a new one as usually:
Store store = context.getBean(Store.class);
objectMapper.readerForUpdating(store).readValue("{\"name\":\"myStore\"}");
Where context is a Spring ApplicationContext reference that I autowired in a test class. You don't need to use the return value of readValue in this case, just inspect the existing store variable and name will be updated.

Allow NULL map values in a custom JsonSerializer

I have a global object mapper in spring whose property is set like this:
getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
This is working correctly and excluding all the null values while serializing.
But I have a custom serializer for a class which has private Map<String, Object> data = new LinkedHashMap<>() as a member variable. This map has keys which have null values which I would like to include in my output. Class level or member level #JsonInclude is not working. Tried overriding isEmpty and returning false always, but it didn't work.
NULL map values were automatically included until Jackson 2.5 but this started happening after upgrade to 2.9+. Any help would be appreciated.
Basically what you are looking for is a custom Serializer only for one class right?
you can find how it is achieved here
And then you can register the serializer to your class
#JsonSerialize(using = CustomSerializer.class)
class YourClass{
This will register the custom serializer to your interested class and this way you can add null values as well.

Using #Value annotation with Spring and SPeL

I am trying to find a way to do the following in my spring boot 1.5 application.
I have a variable who's value is dynamic meaning it comes in from an external system.
String name = "abc"; //gets set externally
I want to try and use the name's value to lookup my property file and see if there is a matching property defined. something like..
#Value("#{myClassName.name.concat('something')}")
String propertyValue;
Now my application.property file has the following property set
assume name has the value "abc"
property file contents:
abc.something:abcValue
Now, when i try to access the value of the variable propertyValue it gets set to the value abc.something and not abcValue.
I probably think I cannot use #Value with #{} to get to that, I was wondering if there was a way to to use #{} inside ${} so that I goes and fetches the property value after calculating the name of the property using #{}.
Let me know if you need more details please.
A bean life-cycle requires properties to be resolved at compile time. So, #Value requires constant parameter.
You can use Environment bean to access your properties programmatically.
import org.springframework.core.env.Environment;
#Service
public class Serivce {
#Autowired
private Environment environment;
public String getProperty(final String keyPart) {
String key = "build.your." + keyPart;
return environment.getProperty(key)
}
}
By the way you can use #('${spring.some.property}') in SpEL to access placeholder.
// This is valid access to property
#Value("#('${spring.some.property}')")
private String property;

Resources