Edited at

MicronautでDIしてみる


MicronautでDI

Micronautには、DI(Dependency Injection)の仕組みがあるらしいです。

Inversion of Control

特徴としては、


  • コンパイル時に生成するデータを使用する(Annotation Processorを利用)

  • リフレクションとプロキシを避ける

  • 起動時間を最適化する

  • メモリフットプリントを減らす

  • 明快で、エラーハンドリングをわかりやすくする

といったものがあるようです。


MicronautのDIで使うスコープ

Micronautでは、アノテーションにJSR-330のものを使用します。

https://docs.oracle.com/javaee/6/api/javax/inject/package-summary.html

JSR-330に含まれる@Singletonに加えて、Micronautで利用するスコープアノテーションは、こちら。

Scopes


  • @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によるリフレッシュができるようです。

Refreshable Scope

このあたりのアノテーションがあるパッケージは、こちら。

https://docs.micronaut.io/1.0.4/api/io/micronaut/context/annotation/package-summary.html

とはいえ、Quick Startで出てくるような@Controllerアノテーションもスコープアノテーションの一種のようなので、全部でこれだけではなさそうですけどね…。

また、DIが可能なコンテナ型(OptionalとかIterableとか)は、こちら。

Injectable Container Types

Qualifiersもあるようです。

Bean 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メソッドを持ったクラスは、自動生成されたままなので省略します。


確認

@SingletonServiceを利用する、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が返ってきていることが確認できます。


※最初、@PrototypeServiceをなにも考えずに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

とりあえず、雰囲気は確認できました。