今日は娘達とマリオをしたりしながらスキマ時間で、ぼーっとMicronautのドキュメントの3.1-3.5までを読んで手を動かしてみた。
https://docs.micronaut.io/latest/guide/index.html#ioc
ソースはここ。
https://github.com/bufferings/hello-mn/tree/20190301-ioc/src/main/java/hello/mn/ioc
Javaは11を使ってて、Micronautは1.0.4を使ってる。
❯ sdk current java Using java version 11.0.1-open ❯ sdk current micronaut Using micronaut version 1.0.4
## 通常のDI
こういう感じでBeanを定義しておいて:
public interface Snack { String name(); } @Singleton public class Chocolate implements Snack { @Override public String name() { return "Chocolate"; } }
こんな感じでDIして使うことができる。他のインジェクション方法も使えるみたいだけど、コンストラクターインジェクションが好きなので、それ以外は僕は使わないと思う:
@Controller("/hello/ioc") public class HelloIocController { private final Snack snack; public HelloIocController(Snack snack) { this.snack = snack; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return snack.name(); } }
実行するとこうなる:
❯ curl localhost:8080/hello/ioc
Chocolate
## Qualifying By Name
Bean定義が複数ある場合は、インジェクトするBeanを名前で指定できる。Engine
を実装したV6Engine
とV8Engine
がこんな風に定義されている場合:
public interface Engine { int getCylinders(); String start(); } @Singleton public class V6Engine implements Engine { int cylinders = 6; @Override public int getCylinders() { return cylinders; } @Override public String start() { return "Starting V6"; } } @Singleton public class V8Engine implements Engine { int cylinders = 8; @Override public int getCylinders() { return cylinders; } @Override public String start() { return "Starting V8"; } }
@Named
アノテーションを使って、こんな風にインジェクトできる:
@Controller("/hello/nq") public class HelloNamedQualifierController { private final Engine v6Engine; private final Engine v8Engine; public HelloNamedQualifierController( @Named("v6") Engine v6Engine, @Named("v8") Engine v8Engine) { this.v6Engine = v6Engine; this.v8Engine = v8Engine; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return v6Engine.start() + ", " + v8Engine.start(); } }
実行するとこうなる:
❯ curl localhost:8080/hello/nq
Starting V6, Starting V8
この辺で「名前のルール」が気になって昼間にメモを残しておいた:
## Qualifying By Annotation
@Named
使う代わりに、自分でアノテーション作ることもできるよ、って書いてあって試した。
@Qualifier @Retention(RUNTIME) public @interface V6 { } @Qualifier @Retention(RUNTIME) public @interface V8 { }
こんな感じで作ると、"V6"とか"V8"のBeanが取得できるみたい。試しにもう一個作ってみた。
@Qualifier @Retention(RUNTIME) public @interface V8Engine { }
"V8Engine"にしたけどこれも動いた。あんまり使うつもりがないから「アノテーション名で判別してるんだろうなー」って雰囲気だけ理解しておいた。こんな風に使える:
@Controller("/hello/aq") public class HelloAnnotationQualifierController { private final Engine v6Engine; private final Engine v8Engine; private final Engine v8Engine2; public HelloAnnotationQualifierController( @V6 Engine v6Engine, @V8 Engine v8Engine, @V8Engine Engine v8Engine2) { this.v6Engine = v6Engine; this.v8Engine = v8Engine; this.v8Engine2 = v8Engine2; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return v6Engine.start() + ", " + v8Engine.start() + ", " + v8Engine2.start(); } }
実行するとこうなる:
❯ curl localhost:8080/hello/aq
Starting V6, Starting V8, Starting V8
まぁ、複数の実装が共通のプレフィックスを持ってる場合とかに便利なのかな?バージョン番号とかでV1
とV2
の2つの実装が沢山ある場合に、このアノテーションを作っておけば便利そうではある。あー、でも、Microserviceって文脈なら、もうそれは別のサービスに分けてルーティングをバージョン分けした方が良さそうか。ま、こういう機能があるんだなってくらいでいっか。
## Primaryアノテーション
複数の実装がある場合に、Primaryアノテーションを使うとそのBeanが優先される。逆の意味のSecondaryもあるみたい。
public interface ColorPicker { String color(); } @Primary @Singleton public class Green implements ColorPicker { @Override public String color() { return "green"; } } @Singleton public class Blue implements ColorPicker { @Override public String color() { return "blue"; } }
普通に使うと、PrimaryのGreenがDIされる。
@Controller("/hello/pt") public class HelloPrimaryTypeController { private final ColorPicker colorPicker; public HelloPrimaryTypeController(ColorPicker colorPicker) { this.colorPicker = colorPicker; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return colorPicker.color(); } }
実行するとこうなる:
❯ curl localhost:8080/hello/pt
green
んー。テストのときに上書きしたくてやるぐらい?しか用途を思いつかん。あー、でもあれか、Blueの方は名前を指定すれば利用できるから、通常はGreenを使うけどたまにBlueを使いたいときとかに便利なのか。ふむふむ。
## Container Type
複数のBeanが定義されているときに、Iterable
とかArray
とかが使えるみたい。あと、Optional
を使うとBeanがない場合にはemptyになるみたい。他にもいくつか対応してるみたい。まぁ、これもあんまり使わんと思うけど一応試しておいた。
@Controller("/hello/ct") public class HelloContainerTypeController { private final List<Engine> engines; private final List<ColorPicker> colorPickers; public HelloContainerTypeController( List<Engine> engines, List<ColorPicker> colorPickers) { this.engines = engines; this.colorPickers = colorPickers; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return engines.stream().map(Engine::start).collect(Collectors.joining(", ")) + ", " + colorPickers.stream().map(ColorPicker::color).collect(Collectors.joining(", ")); } }
実行するとこうなる:
❯ curl localhost:8080/hello/ct
Starting V8, Starting V6, blue, green
## BeanContext
IoCのコンテナはBeanContext
なのかな。実際はそれを継承したApplicationContext
があって、それを実装したDefaultApplicationContext
が使われてる。
こんな風にコンストラクターでInjectしてみたら使えた。
@Controller("/hello/bc") public class HelloBeanContextController { private final BeanContext beanContext; private final ApplicationContext applicationContext; public HelloBeanContextController( BeanContext beanContext, ApplicationContext applicationContext) { this.beanContext = beanContext; this.applicationContext = applicationContext; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return beanContext.getBean(Engine.class, Qualifiers.byName("v6")).start() + ", " + beanContext.getBean(Engine.class, Qualifiers.byName("v8")).start() + ", " + beanContext.getBean(ColorPicker.class).color() + ", " + applicationContext.getBean(Engine.class, Qualifiers.byName("v6")).start() + ", " + applicationContext.getBean(Engine.class, Qualifiers.byName("v8")).start() + ", " + applicationContext.getBean(ColorPicker.class).color(); } }
実行するとこうなる:
❯ curl localhost:8080/hello/bc
Starting V6, Starting V8, green, Starting V6, Starting V8, green
どうなんかなー。使うなら、ApplicationContext
をDIしといたらいいんかな。
## ということで
3.5までを読みながら触ってみたお話でした。
普段は特に複数Beanを定義することもないだろうから、シンプルにBeanを定義してそれをDIして使う、ってぐらいだと思ってる。しばらく時間が取れなさそうだから、また気が向いたときに続き読もうと思う。