概要
このエントリでは、lombokで@RequiredArgsConstructorを使ってコンストラクタインジェクションをしたいときの方法について扱います。
エントリの動機
ちょっとエッジケースかもしれませんが、とはいえやりたくなるケースがほかの方にもあるかもしれませんので参考になればということで。
TL;DR;
タイトルのことをしたい場合には、下記の1行をlombok.configに追加します。
#lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier
説明
使うコード
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());
}
}
この状態で起動しようとすると下記のエラーとなります。
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 --stacktrace option to get the stack trace. 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
lombokの@RequiredArgsConstructorで、MyComponent型のフィールド2つに対してコンストラクタインジェクションで値を指定しようとしていますが、@Beanのnameで指定しているものと違う値であり判別しようがない状況です。
MyServiceをdelombokして、自動生成されるコードを眺めてみます。
java -jar .\lombok.jar delombok --print .\src\main\java\info\beambitious\sandbox\lombokqualifiertest\MyService.java
lombokの@RequiredArgsConstructorが作ってくれたコンストラクタには@Qualifierがついていません。、
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;
}
}
困ったなぁと思ってStackOverflowを眺めていたら、is it possible to add qualifiers in @RequiredArgsConstructor(onConstructor = @__(@Autowired))?というのを見つけました。
(参考):上記記事内で参照されているGitHubのIssue745
TL;DR;のところで紹介した通りの値をlombok.configに指定して、delombokしてみます。
@java.lang.SuppressWarnings("all")
public MyService(@Qualifier("myComponentA") final MyComponent myComponentAdash, @Qualifier("myComponentB") final MyComponent myComponentBdash) {
this.myComponentAdash = myComponentAdash;
this.myComponentBdash = myComponentBdash;
}
今度は、@Qualifierがコンストラクタ内に記述されています。
cleanしてから動かすと、プログラムが正常に動きます。
gradlew clean
gradlew bootRun
(中略)
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
おわりに
別解として、MyService内のフィールドの名前を変えてあげても動くのですが、このエントリではQualifierで明示的に指定する方法について取り上げています。
private final MyComponent myComponentA;
private final MyComponent myComponentB;
このエントリで使用したコードは、GitHubに置いてあります。