Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice for @Value fields, Lombok, and Constructor Injection?

I'm developing a Java Spring application. I have some fields in my application which are configured using a .yml config file. I would like to import those values using an @Value annotation on the fields in question. I would also like to use the best-practice of constructor injection rather than field injection, but I would like to write my constructor using Lombok rather than manually. Is there any way to do all these things at once? As an example, this doesn't work but is similar to what I want to do:

@AllArgsConstructor public class my service {     @Value("${my.config.value}")     private String myField;      private Object myDependency;      ... } 

In this case, what I want is Lombok to generate a constructor which sets only myDependency, and for myField to be read from my config file.

Thanks!

like image 917
Ertai87 Avatar asked Sep 13 '18 21:09

Ertai87


People also ask

Can we use @value in constructor?

In Spring we can use the @Value annotation to set property or arguments values based on a SpEL expression. If we want to use the @Value annotation for a constructor argument we must not forget to add the @Autowired annotation on the constructor as well.

Why is constructor injection better than Autowired?

Constructor injection makes code more robust. It allows us to create immutable objects, preventing NullPointerException s and other errors. You can find the code example on GitHub.

Is Autowired required for constructor injection?

Constructor Injection When using a constructor to set injected properties, you do not have to provide the autowire annotation.

Is Autowired a good practice?

There is nothing wrong with using the @Autowired annotation in production code. You can control the injection with your ApplicationContext as well as some properties on the annotation itself (such as required). So the developer certainly isn't 'giving up' on controlling how things are injected.


Video Answer


2 Answers

You need @RequiredArgsConstructor and mark myDependency as final. In this case, Lombok will generate a constructor based on 'required' final filed as argument, for example:

@RequiredArgsConstructor @Service public class MyService {      @Value("${my.config.value}")     private String myField;      private final MyComponent myComponent;      //... } 

That is equal the following:

@Service public class MyService {      @Value("${my.config.value}")     private String myField;      private final MyComponent myComponent;      public MyService(MyComponent myComponent) { // <= implicit injection         this.myComponent = myComponent;     }       //... } 

Since here is only one constructor, Spring inject MyComponent without the explicit use of the @Autowired annotation.

like image 71
Cepr0 Avatar answered Sep 21 '22 22:09

Cepr0


Male sure you are using at least version 1.18.4 of Lombok. And that you have your desired annotation added to the lombok.config file.

lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Value 

Here is your class:

@AllArgsConstructor(onConstructor = @__(@Autowired)) public class MyService{      @Value("${my.config.value}")     private String myField;      private Object myDependency; } 

And here is the lombok generated class:

public class MyService {  @Value("${my.config.value}") private String myField;  private Object myDependency;  @Autowired @Generated public MyService(@Value("${my.config.value}") final String myField, final Object myDependency) {     this.myField = myField;     this.myDependency = myDependency; } 

PS: Make sure you have the lombok.config file under /src/main/java folder. I tried adding it to /src/main/resources and it did not work.

Response taken from here.

like image 38
octavian Avatar answered Sep 19 '22 22:09

octavian