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

2

64

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!

Uhl answered 13/9, 2018 at 21:23 Comment(1)
Constructor injection is preferred, but I don't believe it's compatible with @ConfigurationProperties, which is the specific best practice here.Theisen
M
60

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.

Moorings answered 13/9, 2018 at 22:27 Comment(8)
How does Spring know which Bean to inject in the field myComponent ? Usually you would have an annotation like @Autowired for that, wouldn't you ?Carrissa
@GustavoPassini If a bean has one constructor, you can omit the @AutowiredMoorings
for those who don't want to limit themselves with the final keyword, they can use @NonNull (lombok) annotation.Luana
Won't this approach take away readability in many cases where you have more than let's say 10 variables, out of which 1 or 2 are getting populated using some code. You would never know which are autowired and which are populated in code?Steerageway
What about the myField which has @Value annotation?Paralyse
How you can test the class with that myField?Lugger
How can I use with @Builder? REpository instance is nullWrenn
I wonder, how we can make this work if we set myField as final ???Luana
S
54

Make 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 Lombok - retain field's annotation in constructor input params.

Scilicet answered 11/4, 2021 at 7:27 Comment(4)
You do not need @Autowired on a constructor.Lenrow
This is how Lombok generated the class. I have no control on the generated code.Scilicet
@Scilicet I think what @Lenrow is pointing out is that @AllArgsConstructor(onConstructor = @__(@Autowired)) adds @Autowired to the constructor, which is unnecessary.Undersurface
For me it works when putting lombok.config in the Gradle/Maven project root folder.Oakum

© 2022 - 2024 — McMap. All rights reserved.