[Java] lombok.config when you want to pass @Qualifier to @RequiredArgsConstructor in lombok

2 minute read

Overview

This entry covers how to do constructor injection with [RequiredArgsConstructor] in lombok.

Motivation for entry

It may be a bit of an edge case, but there may be other people who want to do it, so it’s helpful.

TL;DR;

If you want to use the title, add the following line to lombok.config.

# lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier

Explanation

Code to use

import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.Value;

@RequiredArgsConstructor
@ToString
@Value
public class MyComponent {
    private final String name;
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyComponentConfiguration {
    @Bean(name="myComponentA")
    public MyComponent myComponentA() {
        return new MyComponent("name A");
    }

    @Bean(name="myComponentB")
    public MyComponent myComponentB() {
        return new MyComponent("name B");
    }
}

@Service
@RequiredArgsConstructor
@Slf4j
public class MyService {
    @Qualifier("myComponentA")
    private final MyComponent myComponentAdash;
    @Qualifier("myComponentB")
    private final MyComponent myComponentBdash;

    public void sayHello() {
        log.info("myComponentA->" + myComponentAdash.getName());
        log.info("myComponentB->" + myComponentBdash.getName());
    }
}

If you try to start in this state, the following error will occur.

Error starting ApplicationContext.To display the conditions report re-run your application with'debug' enabled.
2020-07-19 22:00:45.510 ERROR 16076 --- [main] o.s.b.d.LoggingFailureAnalysisReporter:

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in info.beambitious.sandbox.lombokqualifiertest.MyService required a single bean, but 2 were found:
        -myComponentA: defined by method'myComponentA' in class path resource [info/beambitious/sandbox/lombokqualifiertest/MyComponentConfiguration.class]
        -myComponentB: defined by method'myComponentB' in class path resource [info/beambitious/sandbox/lombokqualifiertest/MyComponentConfiguration.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task':bootRun'.
> Process'command' C:\Java\Amazon Corretto\jdk1.8.0_222\bin\java.exe'' finished with non-zero exit value 1

* Try:
Run with --info or --debug option to get more log output.Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 8s
3 actionable tasks: 2 executed, 1 up-to-date

In @RequiredArgsConstructor of lombok, I am trying to specify a value for two fields of MyComponent type by constructor injection, but it is a value different from that specified in name of @Bean and it is impossible to determine.

Let’s delombok MyService and look at the automatically generated code.

java -jar .\lombok.jar delombok --print .\src\main\java\info\beambitious\sandbox\lombokqualifiertest\MyService.java

@Qualifier is not attached to the constructor created by @RequiredArgsConstructor of lombok. ,

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class MyService {
    @java.lang.SuppressWarnings("all")
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MyService.class);
    @Qualifier("myComponentA")
    private final MyComponent myComponentAdash;
    @Qualifier("myComponentB")
    private final MyComponent myComponentBdash;

    public void sayHello() {
        log.info("myComponentA->" + myComponentAdash.getName());
        log.info("myComponentB->" + myComponentBdash.getName());
    }

    @java.lang.SuppressWarnings("all")
    public MyService(final MyComponent myComponentAdash, final MyComponent myComponentBdash) {
        this.myComponentAdash = myComponentAdash;
        this.myComponentBdash = myComponentBdash;
    }
}

If you think you are in trouble and are looking at StackOverflow, is it possible to add qualifiers in @RequiredArgsConstructor(onConstructor = @__(@Autowired))?.

(Reference): Issue745 on GitHub referenced in the above article

Specify the values as introduced in TL;DR; in lombok.config and try delombok.

    @java.lang.SuppressWarnings("all")
    public MyService(@Qualifier("myComponentA") final MyComponent myComponentAdash, @Qualifier("myComponentB") final MyComponent myComponentBdash) {
        this.myComponentAdash = myComponentAdash;
        this.myComponentBdash = myComponentBdash;
    }

This time, @Qualifier is written inside the constructor.

If you move it after cleaning, the program will run normally.

gradlew clean
gradlew bootRun

(Omitted)2020-07-19 22:18:53.006 INFO 14200 --- [main] i.b.s.l.LombokQualifierTestApplication :Started LombokQualifierTestApplication in 1.658 seconds (JVM running for 2.305)
2020-07-19 22:18:53.010 INFO 14200 --- [main] i.b.s.lombokqualifiertest.MyService :myComponentA->name A
2020-07-19 22:18:53.011 INFO 14200 --- [main] i.b.s.lombokqualifiertest.MyService :myComponentB->name B

in conclusion

As another solution, it works even if you rename the field in MyService, but this entry deals with how to specify it explicitly with Qualifier.

     private final MyComponent myComponentA;
     private final MyComponent myComponentB;

The code used in this entry is located on GitHub.