I realized a strange behavior in SpringBoot.
In a yml file I have the following configuration:
main:
record-id:
start-position: 1
value: 1
enabled: true
record-name:
start-position: 2
value: main
enabled: true
invented:
start-position: 3
value: 01012020
enabled: false
And these are the classes for it:
public class FieldType {
private Integer startPosition;
private String value;
private Boolean enabled;
getters/setters
}
#Component
#ConfigurationProperties(prefix = "main")
public class Main {
private FieldType recordId;
private FieldType recordName;
private FieldType invented;
getters/setters <-- sometimes without getters
}
As you can see, the main class has #ConfigurationProperties annotation to load the properties from yml into that bean.
And here is what I have found:
if I don't provide getters for the fields in the main class, then sometimes the fields in the main call stay null, so not initiated
if I restart the SpringBoot, then randomly other (1 or more) fields stay null, so not initiated
if I restart the SpringBoot n times, then, again and again, random fields stay null
if I provide getters for the fields in the main class, then all the fields will be always instantiated from tye yml file, no matter how many times I restart SpringBoot
Why is this? Why SpringBoot requires getters for fields which represent properties in yml?
You don't need getter's to bind the properties, you need setters to bind properties if you are using default constructor, docs
If nested POJO properties are initialized (like the Security field in the preceding example), a setter is not required. If you want the binder to create the instance on the fly by using its default constructor, you need a setter.
In case if you are initializing the FieldType in Main class, then you don't need setters as well
#Component
#ConfigurationProperties(prefix = "main")
public class Main {
private FieldType recordId = new FieldType();
private FieldType recordName = new FieldType();
private FieldType invented = new FieldType();
}
You can also use Constructor binding by completely avoiding setters
public class FieldType {
private Integer startPosition;
private String value;
private Boolean enabled;
public FieldType(Integer startPosition, String value, Boolean enabled) {
this.startPosition = startPosition;
this.value = value;
this.enabled = enabled
}
#ConstructorBinding
#ConfigurationProperties(prefix = "main")
public class Main {
private FieldType recordId;
private FieldType recordName;
private FieldType invented;
public Main(FieldType recordId,FieldType recordName,FieldType invented) {
this.recordId = recordId;
this.recordName = recordName;
this.invented = invented;
}
Just a note on Constructor Binding
To use constructor binding the class must be enabled using #EnableConfigurationProperties or configuration property scanning. You cannot use constructor binding with beans that are created by the regular Spring mechanisms (e.g. #Component beans, beans created via #Bean methods or beans loaded using #Import)
Related
I have a configuration class with this constructor:
public CxfConfigurerImpl(#Value("${cxfclient.timeout.connection}") long connectionTimeout,
#Value("${cxfclient.timeout.connection-request}") long connectionRequestTimeout,
#Value("${cxfclient.timeout.receive}") long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
this.connectionTimeout = connectionTimeout;
this.connectionRequestTimeout = connectionRequestTimeout;
}
But I have different values to set according with my endpoints requests.
Ex:
#Value("${cxfclient.timeout-three.connection}")
private long connectionTimeout3;
#Value("${cxfclient.timeout-three.connection-request}")
private long connectionRequestTimeout3;
#Value("${cxfclient.timeout-three.receive}")
private long receiveTimeout3;
#Autowired
private CxfConfigurerImpl cxfConfigurer3Seg = new CxfConfigurerImpl(connectionTimeout3, connectionRequestTimeout3, receiveTimeout3);
My environment variables are:
CXFCLIENT_TIMEOUT_CONNECTION=1000
CXFCLIENT_TIMEOUT_CONNECTIONREQUEST=1000
CXFCLIENT_TIMEOUT_RECEIVE=1000
CXFCLIENT_TIMEOUTTHREE_CONNECTION=3000
CXFCLIENT_TIMEOUTTHREE_CONNECTIONREQUEST=3000
CXFCLIENT_TIMEOUTTHREE_RECEIVE=3000
The problem is that for the object "cxfConfigurer3Seg" I'm getting the default constructor values (1000). Is there any way to override the values?
OBS: I can't change the "CxfConfigurerImpl" constructor implementation.
Please notes that
#Value : by definition is an Annotation used at the field or method/constructor parameter levelthat indicates a default value expression for the annotated element.
And Note that actual processing of the #Value annotation is performedby a BeanPostProcessor
So values are setted after bean built, otherwise you will already find the value 1000 in your variables
So you should change your implementation here :
You should init configuration in the application level and use #Qualifier for two different bean instead the #Value constructor injection:
#Component
#Getter
#Setter
#ToString
public class B {
private long receiveTimeout;
private long connectionTimeout;
private long connectionRequestTimeout;
public B () {
}
public B ( long connectionTimeout,
long connectionRequestTimeout,
long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
this.connectionTimeout = connectionTimeout;
this.connectionRequestTimeout =
connectionRequestTimeout;
}
}
In the spring boot application startup class : build two beans with two configurations : (b1, b2)
#Value("${cxfclient.timeout-three.connection}")
private long connectionTimeout3;
#Value("${cxfclient.timeout-three.connection-request}")
private long connectionRequestTimeout3;
#Value("${cxfclient.timeout-three.receive}")
private long receiveTimeout3;
#Value("${cxfclient.timeout.connection}")
private long receiveTimeout;
#Value("${cxfclient.timeout.connection-request}")
private long connectionTimeout;
#Value("${cxfclient.timeout.receive}")
private long connectionRequestTimeout;
#Bean ("b1")
public B createAsB1() {
return new B (connectionTimeout3, connectionRequestTimeout3, receiveTimeout3);
}
#Bean ("b2")
public B createAsB2() {
return new B(connectionTimeout, connectionRequestTimeout, receiveTimeout);
}
use them by :
#Component
#Getter
#Setter
#ToString
public class UseB {
#Autowired
#Qualifier ("b1")
private B b1;
#Autowired
#Qualifier ("b2")
private B b2;
}
String d = ab.getB1().toString();
System.out.println(d);
d = ab.getB2().toString();
System.out.println(d);
result of run :
B(receiveTimeout=3000, connectionTimeout=3000,
connectionRequestTimeout=3000)
B(receiveTimeout=1000, connectionTimeout=1000,
connectionRequestTimeout=1000)
There are two options -
1- if you want both the beans available in your application context then you can create two separate beans with two different values.
2- If you want to have both values in your application but only one active bean at a time then you can use #ConditionalOnBean, attaching [here][1] a document for your help. Below is sample rough conditionalonbean code snippet and can enable only one of bean from your configurations file.
#ConditionalOnBean(
"prefix" = "..",
"havingValue" = "true",
"name" = "abc")
public A methodA() {}
#ConditionalOnBean(
"prefix" = "..",
"havingValue" = "false",
"name" = "abc")
public A methodB() {}
How to inject values from yaml and set default values if a property is not defined in the yaml props file? Is using both ConfigurationProperties and Value like below a good use? If not how else can we achieve it?
Properties file
app:
prop1: test
prop2: true
map:
key1: value1
key2: value2
Java class:
#Data
#Validated
#ConfigurationProperties("app")
public class properties {
private String prop1;
#Value("${app.prop2:default}")
private Boolean prop2;
#NestedConfigurationProperty
#NotNull
private Map<String,String> prop3;
}
Is using both ConfigurationProperties and Value like below a good use
No. #ConfigurationProperties indicates to spring that it should bind the java fields based on their name to some matching properties. However to achieve that spring requires that the class that has this annotation must be a spring bean (proxy class). So you have to use it together with either #Configuraiton or #Component or some other annotation that creates a spring bean for this class.
There is also some other functionality available where you can use another annotation #EnableConfigurationProperties(properties.class) on some other spring bean (not the current class that has the #ConfigurationProperties annotation)
Most common is that you use the #Configuration and then spring will be able to create a spring bean (proxy of this class) and then bind the values that you expect on the fields that you have.
Since you already have the #ConfigurationProperties("app"), then the property with the name prop2 will be bound with the application property app.prop2
#Data
#Validated
#Configuration <---------------
#ConfigurationProperties("app")
public class properties {
private String prop1;
private Boolean prop2;
#NestedConfigurationProperty
#NotNull
private Map<String,String> prop3;
}
If your problem is the default values in case the properties do not exist at all in application.properties then you can just initialize those java fields with some values.
#Data
#Validated
#Configuration <---------------
#ConfigurationProperties("app")
public class properties {
private String prop1 = "default";
private Boolean prop2 = false;
#NestedConfigurationProperty
#NotNull
private Map<String,String> prop3;
}
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 the following enum:
public enum MyEnum {
NAME("Name", "Good", 100),
FAME("Fame", "Bad", 200);
private String lowerCase;
private String atitude;
private long someNumber;
MyEnum(String lowerCase, String atitude, long someNumber) {
this.lowerCase = lowerCase;
this.atitude = atitude;
this.someNumber = someNumber;
}
}
I want to setup the someNumber variable different for both instances of the enum using application.properties file.
Is this possible and if not, should i split it into two classes using an abstract class/interface for the abstraction?
You can't/shouldn't change values of a enum in Java. Try using a class instead:
public class MyCustomProperty {
// can't change this in application.properties
private final String lowerCase;
// can change this in application.properties
private String atitude;
private long someNumber;
public MyCustomProperty (String lowerCase, String atitude, long someNumber) {
this.lowerCase = lowerCase;
this.atitude = atitude;
this.someNumber = someNumber;
}
// getter and Setters
}
Than create a custom ConfigurationProperties:
#ConfigurationProperties(prefix="my.config")
public class MyConfigConfigurationProperties {
MyCustomProperty name = new MyCustomProperty("name", "good", 100);
MyCustomProperty fame = new MyCustomProperty("fame", "good", 100);
// getter and Setters
// You can also embed the class MyCustomProperty here as a static class.
// For details/example look at the linked SpringBoot Documentation
}
Now you can change the values of my.config.name.someNumber and my.config.fame.someNumber in the application.properties file. If you want to disallow the change of lowercase/atitude make them final.
Before you can use it you have to annotate a #Configuration class with #EnableConfigurationProperties(MyConfigConfigurationProperties.class). Also add the org.springframework.boot:spring-boot-configuration-processor as an optional dependency for a better IDE Support.
If you want to access the values:
#Autowired
MyConfigConfigurationProperties config;
...
config.getName().getSumeNumber();
Well what you can do is the following:
Create a new class: MyEnumProperties
#ConfigurationProperties(prefix = "enumProperties")
#Getter
public class MyEnumProperties {
private Map<String, Long> enumMapping;
}
Enable ConfigurationProperties to your SpringBootApplication/ any Spring Config via
#EnableConfigurationProperties(value = MyEnumProperties.class)
Now add your numbers in application.properties file like this:
enumProperties.enumMapping.NAME=123
enumProperties.enumMapping.FAME=456
In your application code autowire your properties like this:
#Autowired
private MyEnumProperties properties;
Now here is one way to fetch the ids:
properties.getEnumMapping().get(MyEnum.NAME.name()); //should return 123
You can fetch this way for each Enum value the values defined in your application.properties
I have a properties class below in my spring-boot project.
#Component
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
private String property1;
private String property2;
// getter/setter
}
Now, I want to set default value to some other property in my application.properties file for property1. Similar to what below example does using #Value
#Value("${myprefix.property1:${somepropety}}")
private String property1;
I know we can assign static value just like in example below where "default value" is assigned as default value for property,
#Component
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
private String property1 = "default value"; // if it's static value
private String property2;
// getter/setter
}
How to do this using #ConfigurationProperties class (rather typesafe configuration properties) in spring boot where my default value is another property ?
Check if property1 was set using a #PostContruct in your MyProperties class. If it wasn't you can assign it to another property.
#PostConstruct
public void init() {
if(property1==null) {
property1 = //whatever you want
}
}
In spring-boot 1.5.10 (and possibly earlier) setting a default value works as-per your suggested way. Example:
#Component
#ConfigurationProperties(prefix = "myprefix")
public class MyProperties {
#Value("${spring.application.name}")
protected String appName;
}
The #Value default is only used if not overridden in your own property file.