MicronautでDI
Micronautには、DI(Dependency Injection)の仕組みがあるらしいです。
特徴としては、
- コンパイル時に生成するデータを使用する(Annotation Processorを利用)
- リフレクションとプロキシを避ける
- 起動時間を最適化する
- メモリフットプリントを減らす
- 明快で、エラーハンドリングをわかりやすくする
といったものがあるようです。
MicronautのDIで使うスコープ
Micronautでは、アノテーションにJSR-330のものを使用します。
JSR-330に含まれる@Singleton
に加えて、Micronautで利用するスコープアノテーションは、こちら。
@Singleton
@Context
@Prototype
@Infrastructure
@ThreadLocal
@Refreshable
いくつか、変わったものがありますね。
@Context
スコープというのは、BeanContext
の起動、終了時に初期化、シャットダウンされる必要があるBeanを表します。
※BeanContext
というのは通常ApplicationContext
で、DIのエントリーポイントとなるものです
Micronautでは、SingletonなBeanを遅延初期化するようなので、@Context
アノテーションを使用するとBeanContext
の起動時に初期化することができるようになります。とはいえ、Micronautは起動時に最小限のBeanを作成するように設計されているので、@Context
の仕様は控えめに、ということらしいですが。
@ThreadLocal
とか、変わったものもありますね。@Infrastructure
は、@Singleton
のステレオタイプみたいです。
また、リフレッシュ可能な@Refreshable
というスコープもあり、/refresh
エンドポイントやRefreshEvent
によるリフレッシュができるようです。
このあたりのアノテーションがあるパッケージは、こちら。
とはいえ、Quick Startで出てくるような@Controller
アノテーションもスコープアノテーションの一種のようなので、全部でこれだけではなさそうですけどね…。
また、DIが可能なコンテナ型(Optional
とかIterable
とか)は、こちら。
Qualifiersもあるようです。
ちょっと簡単に、触ってみましょう。
環境
今回の環境は、こちら。
$ mn -V
| Micronaut Version: 1.0.4
| JVM Version: 1.8.0_191
アプリケーションのひな型を作成。
$ mn create-app hello-di --build maven
$ cd hello-di
サンプルアプリケーション
簡単な、@Singleton
、@Prototype
スコープを使ったサンプルを書いてみます。
@Singleton
なService。
src/main/java/hello/di/service/SingletonService.java
package hello.di.service;
import javax.inject.Singleton;
@Singleton
public class SingletonService {
public String message() {
return "Hello Singleton Bean";
}
}
このService
を使用する、Controller
。
src/main/java/hello/di/controller/SingletonBeanController.java
package hello.di.controller;
import hello.di.service.SingletonService;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/singleton")
public class SingletonBeanController {
SingletonService singletonService;
public SingletonBeanController(SingletonService singletonService) {
this.singletonService = singletonService;
}
@Get(produces = MediaType.TEXT_PLAIN)
public String index() {
return singletonService.message() + " from " + singletonService.getClass().getName() + "@" + singletonService.hashCode();
}
}
コンストラクタインジェクションができるようです。
SingletonService singletonService;
public SingletonBeanController(SingletonService singletonService) {
this.singletonService = singletonService;
}
また、インスタンスが同じことを確認するために、ハッシュコードを出してみたり。
@Get(produces = MediaType.TEXT_PLAIN)
public String index() {
return singletonService.message() + " from " + singletonService.getClass().getName() + "@" + singletonService.hashCode();
}
続いて、@Prototype
なService。
src/main/java/hello/di/service/PrototypeService.java
package hello.di.service;
import io.micronaut.context.annotation.Prototype;
@Prototype
public class PrototypeService {
public String message() {
return "Hello Prototype Bean";
}
}
このService
を使用する、Controller
。
src/main/java/hello/di/controller/PrototypeBeanController.java
package hello.di.controller;
import javax.inject.Inject;
import hello.di.service.PrototypeService;
import io.micronaut.context.ApplicationContext;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/prototype")
public class PrototypeBeanController {
@Inject
ApplicationContext applicationContext;
@Get(produces = MediaType.TEXT_PLAIN)
public String index() {
PrototypeService prototypeService = applicationContext.getBean(PrototypeService.class);
return prototypeService.message() + " from " + prototypeService.getClass().getName() + "@" + prototypeService.hashCode();
}
}
Controller
がSingletonらしく、そのままインジェクションすると、@Prototype
なのに同じインスタンスになってしまったので、ApplicatoinContext
から取得することに…。
@Get(produces = MediaType.TEXT_PLAIN)
public String index() {
PrototypeService prototypeService = applicationContext.getBean(PrototypeService.class);
return prototypeService.message() + " from " + prototypeService.getClass().getName() + "@" + prototypeService.hashCode();
}
クライアントプロキシがないからですかねぇ。
また、@Inject
アノテーションが使えるようです。
@Inject
ApplicationContext applicationContext;
ApplicationContext
のコンストラクタインジェクションも、OKです。
PrototypeService prototypeService;
public PrototypeBeanController(PrototypeService prototypeService) {
this.prototypeService = prototypeService;
}
mainメソッドを持ったクラスは、自動生成されたままなので省略します。
確認
@Singleton
なService
を利用する、Controller
にアクセス。
$ curl localhost:8080/singleton
Hello Singleton Bean from hello.di.service.SingletonService@56609473
$ curl localhost:8080/singleton
Hello Singleton Bean from hello.di.service.SingletonService@56609473
$ curl localhost:8080/singleton
Hello Singleton Bean from hello.di.service.SingletonService@56609473
DIが動作し、また同じService
が返ってきていることが、確認できました。
続いて、@Prototype
。
$ curl localhost:8080/prototype
Hello Prototype Bean from hello.di.service.PrototypeService@1219715735
$ curl localhost:8080/prototype
Hello Prototype Bean from hello.di.service.PrototypeService@1284304729
$ curl localhost:8080/prototype
Hello Prototype Bean from hello.di.service.PrototypeService@1807347832
こちらは、異なるインスタンスのService
が返ってきていることが確認できます。
※最初、@Prototype
なService
をなにも考えずにDIしていて、同じインスタンスが返ってきていたことに気づくという…
自動生成されたファイル
ところで、Annotation Processorでどんなファイルが生成されたのでしょう?
$ tree target
target
├── classes
│ ├── META-INF
│ │ ├── hello-di.kotlin_module
│ │ └── services
│ │ └── io.micronaut.inject.BeanDefinitionReference
│ ├── application.yml
│ ├── hello
│ │ └── di
│ │ ├── Application.class
│ │ ├── controller
│ │ │ ├── $PrototypeBeanControllerDefinition$$exec1$$AnnotationMetadata.class
│ │ │ ├── $PrototypeBeanControllerDefinition$$exec1.class
│ │ │ ├── $PrototypeBeanControllerDefinition.class
│ │ │ ├── $PrototypeBeanControllerDefinitionClass$$AnnotationMetadata.class
│ │ │ ├── $PrototypeBeanControllerDefinitionClass.class
│ │ │ ├── $SingletonBeanControllerDefinition$$exec1$$AnnotationMetadata.class
│ │ │ ├── $SingletonBeanControllerDefinition$$exec1.class
│ │ │ ├── $SingletonBeanControllerDefinition.class
│ │ │ ├── $SingletonBeanControllerDefinitionClass$$AnnotationMetadata.class
│ │ │ ├── $SingletonBeanControllerDefinitionClass.class
│ │ │ ├── PrototypeBeanController.class
│ │ │ └── SingletonBeanController.class
│ │ └── service
│ │ ├── $PrototypeServiceDefinition.class
│ │ ├── $PrototypeServiceDefinitionClass$$AnnotationMetadata.class
│ │ ├── $PrototypeServiceDefinitionClass.class
│ │ ├── $SingletonServiceDefinition.class
│ │ ├── $SingletonServiceDefinitionClass$$AnnotationMetadata.class
│ │ ├── $SingletonServiceDefinitionClass.class
│ │ ├── PrototypeService.class
│ │ └── SingletonService.class
│ └── logback.xml
└── generated-sources
└── annotations
9 directories, 25 files
なんか、いろいろ作られてますね。
Service Provider向けのファイルができているようなので、こちらの中身も。
target/classes/META-INF/services/io.micronaut.inject.BeanDefinitionReference
hello.di.service.$PrototypeServiceDefinitionClass
hello.di.service.$SingletonServiceDefinitionClass
hello.di.controller.$SingletonBeanControllerDefinitionClass
hello.di.controller.$PrototypeBeanControllerDefinitionClass
とりあえず、雰囲気は確認できました。