How to group properties with Spring Boot Configuration Properties - java

According to Spring Boot documentation, properties can be grouped and a property may appear in more than one group. But at the moment when we create a property class marked with #ConfigurationProperties(prefix="test1") the group name will be the prefix which is test1. Now if I have another property class for example with prefix as "test2" how can I say this latter one has a property from the group test1?
--- UPDATE ---
Added nested class but it's not working
#Configuration
#Profile({"wmx"})
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "myapp.wmx", locations = {"classpath:application-wmx.properties", "classpath:myapp-env.properties"})
public class WmxProperties {
/**
* The WMX implementation to be loaded.
*/
#NotNull(message = "Must be configured.")
private ProfileEnum profile;
//#ConfigurationProperties(locations = "classpath:myapp-env.properties")
public static class Env {
/**
* Host name for WMX.
*/
private String host;
/**
* Port number for WMX.
*/
//#Pattern(regexp = "^[1-9]\\d*$", message = "Positive port number only.")
private Integer port;
/**
* Provider name.
*/
#NotBlank
private String providerName;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getProviderName() {
return providerName;
}
public void setProviderName(String providerName) {
this.providerName = providerName;
}
}
public ProfileEnum getProfile() {
return profile;
}
public void setProfile(ProfileEnum profile) {
this.profile = profile;
}
}
The commented annotation #ConfigurationProperties on the inner class is done after failing my tests. Spring doesn't load those properties with or without the annotation unless they are in the same property file, in this case application-emx.properties. Why is that? I want to separate these properties
=== RESOLVED ====
I noticed that I had to add a field of type the nested class with getter/setter methods otherwise Spring won't load the properties in the nested class

You can compose them with help of inner classes:
Property file
test1.property1=...
test1.test2.property2=...
test1.test2.property3=...
Java/Spring mapping:
import javax.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
#Getter
#Setter
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(locations = "classpath:myapp.properties")
public class ApplicationProperties {
private String property1;
private Test2 test2;
#Getter
#Setter
#ConfigurationProperties(prefix = "test2")
public static class Test2 {
#NotNull
private String property2;
#NotNull
private String property3;
}
}
We had success with this approach, because java composition mimics structure of property file. Also properties are validatable, so you can fail fast if configuration is not right.
Downside of this approach is that properties are mutable.
If your properties file is getting too big, your application most probably has wider problems.

The annotation processor automatically considers inner classes as nested properties. Make sure you have getters and setters defined.
#ConfigurationProperties(prefix="server")
public class ServerProperties {
private String name;
private Host host;
// ... getter and setters !!!
public static class Host {
private String ip;
private int port;
// ... getter and setters !!!
}
}
The same effect can be achieved with non-inner class but you should use the #NestedConfigurationProperty annotation on a field to indicate that a regular (non-inner) class should be treated as if it were nested.
#ConfigurationProperties(prefix="server")
public class ServerProperties {
private String name;
#NestedConfigurationProperty
private Host host;
// ... getter and setters !!!
}
public class Host {
private String ip;
private int port;
// ... getter and setters
}

Related

Multiple configuration tree to one class in spring boot

i have this in Application.yaml:
override:
email:
enabled: true
value: "test#mycompany.com"
phone:
enabled: true
value: "+420666666666"
How do I make a single class configuration with these values?
I tried this:
public class RecipientOverrideConfig {
#Configuration
#ConfigurationProperties("override.email")
#Data
public class EmailOverride{
Boolean enabled;
String value;
}
#Configuration
#ConfigurationProperties("override.phone")
#Data
public class SmsOverride{
Boolean enabled;
String value;
}
}
But is there a better way to do this?
I suggest making the whole class a ConfigurationProperties
#ConfigurationProperties("override")
public class RecipientOverrideProperties {
private OverrideConfig email;
private OverrideConfig phone;
public class OverrideConfig {
private Boolean enabled;
private String value;
}
// getters and setters were omitted for brevity
}
And then autowire that into your configuration:
#Configuration
public class RecipientOverrideConfig {
#Autowired // or even better, use constructor injection
private RecipientOverrideProperties overrideProperties;
}

How map properties file using #Configuration properites prefix with dotted properties in Spring?

I have a group of properties as follow:
spring.kafka.producer.edwh.bootstrap-servers=localhost:9092
spring.kafka.producer.edwh.properties.enable.idempotence=true
spring.kafka.producer.edwh.retries=10
spring.kafka.producer.edwh.transaction-id-prefix=slv
spring.kafka.producer.edwh.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
spring.kafka.producer.edwh.properties.spring.json.add.type.headers=false
... And I want to map in a class like this by using #ConfigurationProperties(prefix = "spring.kafka.producer.edwh"):
#ConfigurationProperties(prefix = "spring.kafka.producer.edwh")
public class EdwhKafkaProducerConfig {
private String bootstrap_servers;
private String properties_enable_idempotence;
private int retries;
private String transaction_id_prefix;
private String value_serializer;
private boolean properties_spring_json_add_type_headers;
}
... How can I do?
The dotted properties denote separate objects. So if you have
mail.additionalHeaders.redelivery=true
mail.additionalHeaders.secure=true
mail.credentials.username=john
mail.credentials.password=password
Then your config class can look like this:
#ConfigurationProperties(prefix = "mail")
public class ConfigProperties {
private AdditionalHeaders additionalHeaders;
private Credentials credentials;
// getters setters
public class AdditionalHeaders {
private boolean redelivery;
private boolean secure;
// getters setters
}
public class Credentials {
private String username;
private String password;
// getters setters
}
}
Have a look here:
https://www.baeldung.com/configuration-properties-in-spring-boot
If you are using Spring Boot, then you need to annotate your main application class with, in your case:
#EnableConfigurationProperties(value = EdwhKafkaProducerConfig.class)
You may also need to define public accessor methods for your configuration properties.

Prefix for nested configuration properties in spring

Spring boot 2.0.0.RELEASE
I have properties class:
#Configuration
#ConfigurationProperties(prefix="person")
public class PersonProperties {
private AddressProperties addressProperties;
public AddressProperties getAddressProperties() {
return addressProperties;
}
public void setAddressProperties(final AddressProperties addressProperties) {
this.addressProperties = addressProperties;
}
public static class AddressProperties {
private String line1;
public String getLine1() {
return line1;
}
public void setLine1(final String line1) {
this.line1 = line1;
}
}
}
And application.yml:
person:
address:
line1: line1OfAddress
It is not binding properly as my AddressProperties object is null.
When a class has the same name as yml properties AddressProperties -> Address it is working well. I tried to add Qualifier or ConfigurationProperties with a prefix address but it is not working. Unfortunately, I cannot find useful information about this case in spring docs.
How to specify a prefix for nested properties?
Property defined in yaml / property file should match with the variables defined in class.
Either change yaml file as
person:
# addressProperties will also work here
address-properties:
line1: line1OfAddress
Or define your bean as
#Configuration
#ConfigurationProperties(prefix = "person")
public class PersonProperties {
// here variable name doesn't matter, it can be addressProperties as well
// setter / getter should match with properties in yaml
// i.e. getAddress() and setAddress()
private AddressProperties address;
public AddressProperties getAddress() {
return address;
}
public void setAddress(AddressProperties address) {
this.address = address;
}
}
If you want to get all properties under address without defining them in separate bean you can define your PersonProperties class as
#Configuration
#ConfigurationProperties(prefix = "person")
public class PersonProperties {
private Map<String, Object> address;
public Map<String, Object> getAddress() {
return address;
}
public void setAddress(Map<String, Object> address) {
this.address = address;
}
}
Here PersonProperties#address will contain {line1=line1OfAddress}
Now All properties under address will be in the Map.
You could simply un-nest the two classes, allowing each to have it's own prefix.
First class:
#Configuration
#ConfigurationProperties(prefix="person")
public class PersonProperties {
private AddressProperties addressProperties;
public AddressProperties getAddressProperties() {
return addressProperties;
}
public void setAddressProperties(final AddressProperties addressProperties) {
this.addressProperties = addressProperties;
}
}
Second class:
#Configuration
#ConfigurationProperties(prefix="person.address")
public class PersonAddressProperties {
private String line1;
public String getLine1() {
return line1;
}
public void setLine1(final String line1) {
this.line1 = line1;
}
}
Edit: As was pointed out in the comments, you'd have to inject both of these classes if one block of code needed to refer to both sets of properties.

Spring boot YAML Config not reading all values

I'm trying to setup and use a YAML as config file in my Spring Boot 1.5.1 project.
My YAML file looks like the following:
hue:
user: cdKjsOQIRY8hqweAasdmx-WMsn
ip: "http://192.168.1.69"
scenes:
sunstatus:
enabled: true
id: 93yv8JekmAneCU9
group: 1
disable:
enabled: true
id: 93yv8JekmAneCU9
group: 6
It works perfectly fine to read hue.getUser(). However, hue.getScenes() returns null for some reason. My Java code for the Hue Config looks like the following:
#Configuration
#ConfigurationProperties(prefix = "hue")
public class Hue {
private String user;
private String ip;
private Scenes scenes;
/*
* Getters and setters of course
*/
public class Scenes {
private Sunstatus sunstatus;
private Disable disable;
/*
* Getters and setters
*/
public class Sunstatus {
private boolean enabled;
private String id;
private String group;
/*
* Getters and setters
*/
}
public class Disable {
private boolean enabled;
private String id;
private String group;
/*
* Getters and setters
*/
}
}
}
I've tried as well to annotate the each class with prefix as well, both in the format of hue.scenes.sunstatus, scenes.sunstatus and just sunstatus as well.
Additionally I also tried to use the #Value annotation a bit without any luck.
It's the same results if I keep the data in application.yml or in an external file. Can always only reach getUser().
What am I doing wrong here?
I see you are using public non-inner classes for nested configuration, so you should add the #NestedConfigurationProperty instead:
public class Scenes {
#NestedConfigurationProperty
private Sunstatus sunstatus;
#NestedConfigurationProperty
private Disable disable;
Nested properties
You can use the #NestedConfigurationProperty annotation on a field to indicate that a regular (non-inner) class should be treated as if it were nested.
So either add the annotations (if you plan on using the classes elsewhere) or make them public static.
try this.
#Configuration
#ConfigurationProperties(prefix = "hue")
public class Hue {
private String user;
private String ip;
private Scenes scenes = new Scenes();
/*
* Getters and setters of course
*/
public class Scenes {
private Sunstatus sunstatus = new Sunstatus();
private Disable disable = new Disable();
/*
* Getters and setters
*/
public class Sunstatus {
private boolean enabled;
private String id;
private String group;
/*
* Getters and setters
*/
}
public class Disable {
private boolean enabled;
private String id;
private String group;
/*
* Getters and setters
*/
}
}
}

How to exclude property from Lombok builder?

I have a class called as "XYZClientWrapper" , which have following structure:
#Builder
XYZClientWrapper{
String name;
String domain;
XYZClient client;
}
What I want no build function generated for property XYZClient client
Does Lombok supports such use case?
Yes, you can place #Builder on a constructor or static (factory) method, containing just the fields you want.
Disclosure: I am a Lombok developer.
Alternatively, I found out that marking a field as final, static or static final instructs #Builder to ignore this field.
#Builder
public class MyClass {
private String myField;
private final String excludeThisField = "bar";
}
Lombok 1.16.10
Create the builder in code and add a private setter for your property.
#Builder
XYZClientWrapper{
String name;
String domain;
XYZClient client;
public static class XYZClientWrapperBuilder {
private XYZClientWrapperBuilder client(XYZClient client) { return this; }
}
}
Here is my preferred solution. With that, you can create your field client at the end and have it depending on other fields that previously set by the builder.
XYZClientWrapper{
String name;
String domain;
XYZClient client;
#Builder
public XYZClientWrapper(String name, String domain) {
this.name = name;
this.domain = domain;
this.client = calculateClient();
}
}
For factory static method example
class Car {
private String name;
private String model;
private Engine engine; // we want to ignore setting this
#Builder
private static Car of(String name, String model){
Car car=new Car();
car.name = name;
car.model = model;
constructEngine(car); // some static private method to construct engine internally
return car;
}
private static void constructEngine(Car car) {
// car.engine = blabla...
// construct engine internally
}
}
then you can use as follows:
Car toyotaCorollaCar=Car.builder().name("Toyota").model("Corolla").build();
// You can see now that Car.builder().engine() is not available
Notice the static method of will be called whenever build() is called, so doing something like Car.builder().name("Toyota") won't actually set the value "Toyota" into name unless build() is called and then assigning logic within the constructor static method of is executed.
Also, Notice that the of method is privately accessed so that build method is the only method visible to the callers
I found that I was able to implement a "shell" of the static Builder class, add the method I want to hide with a private access modifier, and it is no longer accessible in the builder. Likewise I can add custom methods to the builder as well.
package com.something;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import java.time.ZonedDateTime;
#Data
#Entity
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class MyClass{
//The builder will generate a method for this property for us.
private String anotherProperty;
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "localDateTime", column = #Column(name = "some_date_local_date_time")),
#AttributeOverride(name = "zoneId", column = #Column(name = "some__date_zone_id"))
})
#Getter(AccessLevel.PRIVATE)
#Setter(AccessLevel.PRIVATE)
private ZonedDateTimeEmbeddable someDateInternal;
public ZonedDateTime getSomeDate() {
return someDateInternal.toZonedDateTime();
}
public void setSomeDate(ZonedDateTime someDate) {
someDateInternal = new ZonedDateTimeEmbeddable(someDate);
}
public static class MyClassBuilder {
//Prevent direct access to the internal private field by pre-creating builder method with private access.
private MyClassBuilder shipmentDateInternal(ZonedDateTimeEmbeddable zonedDateTimeEmbeddable) {
return this;
}
//Add a builder method because we don't have a field for this Type
public MyClassBuilder someDate(ZonedDateTime someDate) {
someDateInternal = new ZonedDateTimeEmbeddable(someDate);
return this;
}
}
}
I found one more solution
You can wrap your field into initiated final wrapper or proxy.
The easiest way to wrap it into AtomicReference.
#Builder
public class Example {
private String field1;
private String field2;
private final AtomicReference<String> excluded = new AtomicReference<>(null);
}
You can interact with it inside by get and set methods but it won't be appeared in builder.
excluded.set("Some value");
excluded.get();
Adding a so called 'partial builder' to the class with Lombok #Builder can help. The trick is to add a inner partial builder class like this:
#Getter
#Builder
class Human {
private final String name;
private final String surname;
private final Gender gender;
private final String prefix; // Should be hidden, depends on gender
// Partial builder to manage dependent fields, and hidden fields
public static class HumanBuilder {
public HumanBuilder gender(final Gender gender) {
this.gender = gender;
if (Gender.MALE == gender) {
this.prefix = "Mr.";
} else if (Gender.FEMALE == gender) {
this.prefix = "Ms.";
} else {
this.prefix = "";
}
return this;
}
// This method hides the field from external set
private HumanBuilder prefix(final String prefix) {
return this;
}
}
}
PS: #Builder allows the generated builder class name to be changed. The example above assumed the default builder class name is used.
I have another approach using #Delegate and Inner Class, which supports "computed values" for the excluded fields.
First, we move the fields to be excluded into an Inner Class to avoid Lombok from including them in the Builder.
Then, we use #Delegate to expose Getters/Setters of the builder-excluded fields.
Example:
#Builder
#Getter #Setter #ToString
class Person {
private String name;
private int value;
/* ... More builder-included fields here */
#Getter #Setter #ToString
private class BuilderIgnored {
private String position; // Not included in the Builder, and remain `null` until p.setPosition(...)
private String nickname; // Lazy initialized as `name+value`, but we can use setter to set a new value
/* ... More ignored fields here! ... */
public String getNickname(){ // Computed value for `nickname`
if(nickname == null){
nickname = name+value;
}
return nickname;
}
/* ... More computed fields' getters here! ... */
}
#Delegate #Getter(AccessLevel.NONE) // Delegate Lombok Getters/Setters and custom Getters
private final BuilderIgnored ignored = new BuilderIgnored();
}
It will be transparent to outside of this Person class that position and nickname are actually inner class' fields.
Person p = Person.builder().name("Test").value(123).build();
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=null, nickname=Test123))
p.setNickname("Hello World");
p.setPosition("Manager");
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=Manager, nickname=Hello World))
Pros:
Do not force the excluded fields to be final
Support computed values for the excluded fields
Allow computed fields to refer to any fields set by the builder (In other words, allow the inner class to be non-static class)
Do not need to repeat the list of all fields (Eg. listing all fields except the excluded ones in a constructor)
Do not override Lombok library's #Builder (Eg. creating MyBuilder extends FooBuilder)
Cons:
The excluded fields are actually fields of Inner Class; however, using private identifier with proper Getters/Setters you can mimic as if they were real fields
Therefore, this approach limits you to access the excluded fields using Getters/Setters
The computed values are lazy initialized when Getters are invoked, not when .build().
One method I like and use is this.
Keep required parameters in constructor, and set optional through builder. Works if number of required is not very big.
class A {
private int required1;
private int required2;
private int optional1;
private int optional2;
public A(int required1, int required2) {
this.required1 = required1;
this.required2 = required2;
}
#Builder(toBuilder = true)
public A setOptionals(int optional1, int optional2) {
this.optional1 = optional1;
this.optional2 = optional2;
return this;
}
}
And then construct it with
A a = new A(1, 2).builder().optional1(3).optional2(4).build();
Nice thing with this approach is that optionals can also have default value.
One approach I have used before was to group instance fields into Configuration fields and Session fields. Configuration fields go as class instances and are visible to the Builder, while Session fields go into a nested private static class and are accessed via a concrete final instance field (which the Builder will ignore by default).
Something like this:
#Builder
class XYZClientWrapper{
private String name;
private String domain;
private static class Session {
XYZClient client;
}
private final Session session = new Session();
private void initSession() {
session.client = ...;
}
public void foo() {
System.out.println("name: " + name);
System.out.println("domain: " + domain;
System.out.println("client: " + session.client);
}
}
To exclude field from builder, try using #Builder.Default

Categories