In a Spring Boot application, Spring Boot is used to build a Properties object from a YAML file as follows:
YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean();
yamlFactory.setResources(new DefaultResourceLoader().getResource("application.yml"));
Properties properties = yamlFactory.getObject();
The reason why Spring Boot's own parser is used is that it not only reads YAML-compliant settings, but also dot-notated properties like e.g:
artist.elvis.name: "Elvis"
artist.elvis.message: "Aloha from Hawaii"
Now that the Properties object is built, I want to map it into an object like the following for example:
#JsonIgnoreProperties(ignoreUnknown = true)
private record Artist(Elvis elvis) {
private record Elvis(String name, String message) { }
}
My question is:
How can this be done with Jackson? Or is there another/better solution for this?
Many thanks for any help
I saw functionality like that in Ratpack framework.
e.g.:
var propsFileUrl =
Thread.currentThread()
.getContextClassLoader()
.getResource("application.properties");
ApplicationProperties applicationProperties =
ConfigData.builder()
.props(propsFileUrl)
.build()
.get(ApplicationProperties.class);
under the hood it is indeed done by using jackson's object mapper, but the logic is not as trivial to post it here.
here's the library:
https://mvnrepository.com/artifact/io.ratpack/ratpack-core/2.0.0-rc-1
application.yml is the default yml file, so no custom configuration is required. Value annotation should be able to read the properties.
#Value("${artist.elvis.name}")
private String name;
Next part I am not sure about your requirements, but hope this is what you are looking for.
To bind to this object 'constructor' can be a good option.
Class for elvis
#Bean
public class Elvis {
private String name;
private String message;
public Elvis(#Value("${artist.elvis.name"}) final String name, #Value("${artist.elvis.message"}) final String message) {
this.name=name;
this.message=message
}
// getter setter for name and message
}
Now Autowire the created bean to Artist bean
#Bean("artists")
public class Artists {
#Autowired
private Elvis elvis
pubic Elvis getElvis() {
return elvis;
}
}
Related
I have a configuration class like below. All of fields in the inner class OptionalServiceConfigs has a default value as annotated using #Value as shown in below.
Sometimes in my application.properties file, it does not have a single service prefixed property. In that case, we want to have loaded an OptionalServiceConfigs instance with its default field values.
#Configuration
#ConfigurationProperties(prefix = "myconf")
public class MyConfigs {
// ... rest of my configs
#Value("${service:?????}") // what to put here, or can I?
private OptionalServiceConfigs service; // this is null
// In this class all fields have a default value.
public static class OptionalServiceConfigs {
#Value("${mode:local}")
private String mode;
#Value("${timeout:30000}")
private long timeout;
// ... rest of getter and setters
}
// ... rest of getter and setters
}
But unfortunately, the service field is null when it is accessed using its getter method. Because spring boot does not initialize an instance of it when there is no property keys found with prefixed myconf.service.* in my application.properties file.
Question:
How can I make service field to initialize to a new instance along with its specified default field values when there are no corresponding prefixed keys in properties file?
I can't imagine a value to put in annotation #Value("${service:?????}") for service field.
Nothing works, tried, #Value("${service:}") or #Value("${service:new")
Based on #M. Deinum's advice, did some changes to configuration class. I am a newbie to Spring and it seems I have misunderstood how Spring works behind-the-scenes.
First I removed all #Value annotation from inner class (i.e. OptionalServiceConfigs), and as well as service field in MyConfigs class.
Then, initialized all inner class fields with their default values inline.
In the constructor of MyConfigs, I initialized a new instance of OptionalServiceConfigs for the field service.
By doing this, whenever there is no service related keys in my application.properties a new instance has already been created with default values.
When there is/are service related key/s, then Spring does override my default values to the specified values in application.properties only the field(s) I've specified.
I believe from Spring perspective that there is no way it can know in advance that a referencing field (i.e. service field) would be related to the configurations, when none of its keys exist in the configuration file. That must be the reason why Spring does not initialize it. Fair enough.
Complete solution:
#Configuration
#ConfigurationProperties(prefix = "myconf")
public class MyConfigs {
// ... rest of my configs
private OptionalServiceConfigs service;
public static class OptionalServiceConfigs {
private String mode = "local";
private long timeout = 30000L;
// ... rest of getter and setters
}
public MyConfigs() {
service = new OptionalServiceConfigs();
}
// ... rest of getter and setters
}
you can try such a structure which works for me quite fine:
#Data
#Validated
#ConfigurationProperties(prefix = "gateway.auth")
#Configuration
public class AuthProperties {
#NotNull
private URL apiUrl;
#Valid
#NotNull
private Authentication authentication;
#Data
public static class Authentication {
#NotNull
private Duration accessTokenTtl;
#NotNull
private String accessTokenUri;
#NotNull
private String clientId;
#NotNull
private String clientSecret;
#NotNull
private String username;
#NotNull
private String password;
#Min(0)
#NonNull
private Integer retries = 0;
}
}
Important is to have getters and setters in order to enable Spring to postprocess ConfigurationProperties, I am using Lombok (#Data) for this.
please see here for more details:
Baeldung ConfigurationProperties Tutorial
I have two bean classes like below
package com.abc;
public class Employee{
private String id;
private String name;
//setters & getters
}
and
package com.cda;
public class EmployeeDTO{
private String id;
private String name;
//setters & getters
}
I want to set the property fields from Employee to EmployeeDTO using spring xml configuration. Where data is coming from some other sources to the Employee Object.
Can you please help me on this scenario.
Use BeanUtils from apache or spring framework instead. Be careful about the argument positioning in these two ways. They are exactly opposite:
org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)
OR
org.springframework.beans.BeanUtils.copyProperties(Object source, Object target)
this is actually not a task that spring does for you. Spring is more about wiring all depending objects together that work together during runtime. What you need is a mapper like mapstruct or enter link description here.
Lets consider a scenario
Where Employee gets it data from a datasource and now you want to map it to EmployeeDto
In such cases:
You can add a constructor in EmployeeDto which takes Employee as parameter and maps the field
You can use ModelMapper where a simple line like:EmployeeDto employeeDto = modelMapper.map(employee, EmployeeDto.class); will work
Use BeanUtils import org.apache.commons.beanutils.BeanUtils;
EmployeeDto newObject = new EmployeeDto();
BeanUtils.copyProperties(newObject, oldObject); reference
Use Jackson ObjectMapper by the convertValue() method: (not recommended due to performance issues)
ObjectMapper mapper = new ObjectMapper();
Employee employee = ...;
EmployeeDto employeeDto = mapper.convertValue(employee, EmployeeDto.class);
I have a bean class like, for example
class Sample {
private String message;
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
and I want to set this property's value.
In Xml Configuration, I could do
<bean id = "sample" class = "Sample"
<property name = "message" value = "Hello there!"/>
</bean>
How do I achieve the same thing i.e. set property's value using Java Annotation? Now I have read that we can use #Value annotation using some properties file but cannot it be done without using properties file, doing the way I did it through xml file? Or using properties file is necessary?
I was able to do it by including #Value("Hello there!") above the setter method. But I could feel that is not a good idea. How to set the property values for different instances using Java Annotations?
Thanks.
The value inserted into the #Value can come from places other than a properties file, for example it can also use system properties.
Using the guide here as a starting point should help you understand a little better.
As a basic and mostly useless usage example we can only inject “string
value” from the annotation to the field:
#Value("string value")
private String stringValue;
Using the #PropertySource annotation allows us to work with values
from properties files with the #Value annotation. In the following
example we get “Value got from the file” assigned to the field:
#Value("${value.from.file}")
private String valueFromFile;
We can also set the value from system properties with the same syntax.
Let’s assume that we have defined a system property named systemValue
and look at the following sample:
#Value("${systemValue}")
private String systemValue;
Default values can be provided for properties that might not be
defined. In this example the value “some default” will be injected:
#Value("${unknown.param:some default}")
private String someDefault;
You have a few options, depending on your requirements. In both of these examples you can set the annotation on a setter instead of the field.
Custom PropertySource
This lets you continue using #Value with greater control for how the properties are supplied. There are a large number of PropertySource implementations, but you can always create your own.
References:
How to add custom property source to Spring's Environment
PropertySource JavaDoc
Example:
#Configuration
class MyConfiguration {
#Bean
PropertySource myPropertySource(ConfigurableEnvironment env) {
MapPropertySource source = new MapPropertySource("myPropertySource", singletonMap("myPropertyValue", "example"));
env.getPropertySources().addFirst(source);
return source;
}
}
class Sample {
#Value("${myPropertyValue}")
private String message;
public String getMessage() {
return message;
}
}
String Bean
Define a bean as a String and auto-wire it using its qualifier.
Example:
#Configuration
class MyConfiguration {
#Bean
String myPropertyValue() {
String value;
// do something to get the value
return value;
}
}
class Sample {
#Autowired
#Qualifier("myPropertyValue")
private String message;
public String getMessage() {
return message;
}
}
I have a configuration parameter "myconfig.defaultSize" whose value is defined, for example, as "10MB", in the application.properties file.
In the other hand, I have a #Component class with #ConfigurationProperties annotation mapping those configuration parameter, as follows.
#Component
#ConfigurationProperties(prefix="myconfig")
public class StorageServiceProperties {
private Long defaultSize;
//...getters and setters
}
So, how can I would apply a method to convert the String value into Long?
You can't have such generic converter applied on a property-to-property basis. You could register a converter from String to Long but it would be called for every such case (any property of type Long basically).
The purpose of #ConfigurationProperties is to map the Environment to a higher-level data structure. Perhaps you could do that there?
#ConfigurationProperties(prefix="myconfig")
public class StorageServiceProperties {
private String defaultSize;
// getters and setters
public Long determineDefaultSizeInBytes() {
// parsing logic
}
}
If you look at the multipart support in Spring Boot, we keep the String value and we use the #ConfigurationProperties object to create a MultipartConfigElement that is responsible of the parsing. That way you can specify those special values in code and configuration.
public void setDefaultSize(String defaultSize) {
try {
this.defaultSize = Long.valueOf(defaultSize);
} catch (NumberFormatException e) {
// handle the exception however you like
}
}
Suppose you have this entity:
class Foo{
String propA;
String propB;
}
and you want to serialize for one API like :
{propA: "ola",
propB: "Holla"}
and for another API like :
{fooPropA: "ola",
fooPropB: "Holla"}
How can this be achieved using jackson and using the same entity. Creating 2 different entities is not an option :)
There are several ways in which you can achieve this. You can enable a custom serializer (already covered by #se_vedem), register an annotation introspector which changes the property names for the corresponding class and so on.
However, if you are willing to only add a string prefix to all the property names, then the Jackson property name strategy is probably the best fit. The naming strategy class has the access to the serialized object type information, so you can make a decision whether to change the property name or not.
Here is an example using a custom annotation that defines the prefix:
public class JacksonNameStrategy {
#Retention(RetentionPolicy.RUNTIME)
public static #interface PropertyPrefix {
String value();
}
#PropertyPrefix("foo_")
public static class Foo {
public String propA;
public String propB;
public Foo(String propA, String propB) {
this.propA = propA;
this.propB = propB;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new MyPropertyNamingStrategyBase());
System.out.println(mapper.writeValueAsString(new Foo("old", "Holla")));
}
private static class MyPropertyNamingStrategyBase extends PropertyNamingStrategy {
#Override
public String nameForField(MapperConfig<?> config,
AnnotatedField field,
String defaultName) {
PropertyPrefix ann = field.getDeclaringClass().getAnnotation(PropertyPrefix.class);
if (ann != null) {
return ann.value() + defaultName;
}
return super.nameForField(config, field, defaultName);
}
}
}
Output:
{"foo_propA":"old","foo_propB":"Holla"}
In your API method you choose between two ObjectMapper instances one with the default naming naming strategy and one with the custom one.
You can achieve this by using modules feature from Jackson.
Basically, each API would have it's own ObjectMapper and they will be configured with different modules. This way you can create 2 serializers for the same class and register them on the appropriate module. More read can be found here http://wiki.fasterxml.com/JacksonFeatureModules
However, be aware that serializers are loaded in a particular order. First it tries to get the annotated ones, if none is found it will try to get those registered from modules. So, for example if you have your class annotated with serializer, then that serializer(FooSerializer) would be chosen instead of the one configured in module(MySecondFooSerializer).
#JsonSerialize(using = FooSerializer.class)
class Foo{
String propA;
String propB;
}
module.addSerializer(Foo.class, new MySecondFooSerializer());