Micronautのコンパイル時依存解決の仕組みを逆コンパイルで見る

# はじめに

先日、[Micronaut 1.0がリリース](https://objectcomputing.com/news/2018/10/23/micronaut-10-ga-released)されました。
Micronautは、起動が高速なフルスタックフレームワークと言われています。
SpringのようなリフレクションベースのIoCコンテナではなく、コンパイル時に依存関係を解決することで、高速に起動できるとのこと。

実際にどれだけ高速になるかは別の記事を参考にするとして、本記事では、コンパイル時にどのように依存解決しているのか、逆コンパイルをして確かめてみようと思います。

# 準備

[ドキュメント](https://docs.micronaut.io/latest/guide/index.html)に従ってプロジェクトを作成します。
ここではMavenプロジェクトを作成します。

```
mn create-app --build maven hello-world
```

maven-compiler-pluginのconfigurationに、micronautのプラグインがアノテーションプロセッサとして登録されていました。

```xml:pom.xml
<annotationProcessorPaths>
  <path>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-inject-java</artifactId>
    <version>${micronaut.version}</version>
  </path>
  <path>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-validation</artifactId>
    <version>${micronaut.version}</version>
  </path>
</annotationProcessorPaths>
```

今回はこんな構成でサンプルコードを作りました。
resourcesとかtestとかは省いています。

```
hello-world
│ pom.xml
│
└─src
   └─main
      └─java
         └─hello
             └─world
                     Application.java
                     SampleController.java
                     SampleService.java

```

それぞれのファイルの中身はこんな感じです。
`/hello` にアクセスされたら、 `"Hello"` を返すだけの単純なプログラムです。

```java:Application.java
package hello.world;

import io.micronaut.runtime.Micronaut;

public class Application {

    public static void main(String... args) {
        Micronaut.run(Application.class);
    }

}
```

```java:SampleController.java
package hello.world;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;

@Controller("/hello")
public class SampleController {

    private final SampleService sampleService;

    public SampleController(SampleService sampleService) {
        this.sampleService = sampleService;
    }

    @Get(produces = MediaType.TEXT_PLAIN)
    public String hello() {
        return sampleService.getHello();
    }

}
```

```java:SampleService.java
package hello.world;

import javax.inject.Singleton;

@Singleton
public class SampleService {

    public String getHello() {
        return "Hello";
    }

}
```

# コンパイルと解析

`mvn clean package` でビルドします。
すると、 `target/classes` に次のクラスファイルが生成されます。

```
$SampleControllerDefinition$$exec1$$AnnotationMetadata.class
$SampleControllerDefinition$$exec1.class
$SampleControllerDefinition.class
$SampleControllerDefinitionClass$$AnnotationMetadata.class
$SampleControllerDefinitionClass.class
$SampleServiceDefinition.class
$SampleServiceDefinitionClass$$AnnotationMetadata.class
$SampleServiceDefinitionClass.class
Application.class
SampleController.class
SampleService.class
```

何やらたくさんのクラスファイルが生成されています。
下3つの `Application.class` 、 `SampleController.class` 、 `SampleService.class` は自作クラスなので良し。
他のクラスについて、ピックアップで記載します。

## 依存関係を解決する処理

「～DefinitionClass」で行っています。
SampleControllerのDefinitionClassを見てみましょう。

```java:\$SampleControllerDefinitionClass
package hello.world;

import io.micronaut.context.AbstractBeanDefinition;
import io.micronaut.context.BeanContext;
import io.micronaut.context.BeanResolutionContext;
import io.micronaut.context.DefaultBeanContext;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.type.Argument;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanFactory;
import io.micronaut.inject.annotation.DefaultAnnotationMetadata;
import java.util.Collections;
import java.util.Map;

public class $SampleControllerDefinition extends AbstractBeanDefinition<SampleController> implements BeanFactory<SampleController> {
    protected $SampleControllerDefinition(Class<SampleController> type, AnnotationMetadata constructorAnnotationMetadata, boolean requiresReflection, Argument[] arguments) {
        super(type, constructorAnnotationMetadata, requiresReflection, arguments);
        super.addExecutableMethod(new $SampleControllerDefinition$$exec1());
    }

    public $SampleControllerDefinition() {
        this(SampleController.class,
             new DefaultAnnotationMetadata(/* 長いので省略 */),
             false,
             new Argument[] {
                 Argument.of(SampleService.class,
                             "sampleService",
                             (AnnotationMetadata)null,
                             (Argument[])null)
             });
    }

    @Override
    public SampleController build(BeanResolutionContext resolutionContext, BeanContext context, BeanDefinition<SampleController> definition) {
        SampleController instance = new SampleController((SampleService)super.getBeanForConstructorArgument(resolutionContext, context, 0));
        instance = (SampleController)this.injectBean(resolutionContext, context, instance);
        return instance;
    }

    @Override
    protected Object injectBean(BeanResolutionContext resolutionContext, BeanContext context, Object bean) {
        SampleController instance = (SampleController)bean;
        return super.injectBean(resolutionContext, (DefaultBeanContext)context, bean);
    }

    @Override
    protected AnnotationMetadata resolveAnnotationMetadata() {
        return $SampleControllerDefinitionClass.$ANNOTATION_METADATA;
    }
}
```

`SampleController` のコンストラクタの引数に渡すオブジェクトの情報は、`$SampleControllerDefinition()` デフォルトコンストラクタの中で `Argument` として、Micronautに渡しています。
`build()` メソッドで、コンストラクタインジェクションが行われています。
当然ながら、リフレクションではなく `new SampleController()` していますね。

今回のコードでは存在しませんが、 `injectBean()` メソッドの中で、他のインジェクションを行います。

また、もう1つの `$SampleControllerDefinition()` コンストラクタの中で、APIの受け口である `hello()` を表す `$SampleControllerDefinition$$exec1` をMicronautに渡しています。
このクラスについては次の節で記載します。

## 実行ポイントの登録

`SampleController` の `hello()` メソッドに対する情報（アノテーションのパラメータ等）が、 `$SampleControllerDefinition$$exec1$$AnnotationMetadata` として生成されます。
今回はAPIは1つですが、2つ以上のAPIが存在する場合は、 `exec2` 、 `exec3` ・・・とAPIの数だけクラスが生成されます。
API実行の処理は、 `$SampleControllerDefinition$$exec1` にあります。

```java:\$SampleControllerDefinition\$\$exec1
package hello.world;

import io.micronaut.context.AbstractExecutableMethod;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.type.Argument;

public class $SampleControllerDefinition$$exec1 extends AbstractExecutableMethod {
    public static final AnnotationMetadata $ANNOTATION_METADATA = new $SampleControllerDefinition$$exec1$$AnnotationMetadata();

    public $SampleControllerDefinition$$exec1() {
        super(SampleController.class, "hello", Argument.of(String.class, "hello"));
    }

    @Override
    public Object invokeInternal(Object instance, Object[] arguments) {
        return ((SampleController)instance).hello();
    }

    protected AnnotationMetadata resolveAnnotationMetadata() {
        return $ANNOTATION_METADATA;
    }
}
```

Micronautが `invokeInternal()` メソッドを呼ぶことで、APIが実行されます。

ここまで、確かにリフレクションを使わないコードとなっていました。

## 注意点

今回のサンプルにはありませんが、フィールドへのインジェクションを行う際は、リフレクションが使われます。
生成されるコードではありませんが、「～DefinitionClass」の `injectBean()` メソッドの中でMicronautの `AbstractBeanDefinition#injectBeanField()` メソッドが呼ばれるようになり、このメソッドの中でリフレクションを使ってインジェクションしています。

当然と言えば当然ですが、せっかくのMicronautのメリットを最大限に活かしきれないため、フィールドインジェクションではなくコンストラクタインジェクションを使うのが良いでしょう。

# まとめ

コードを読み解いて確認しましたが、確かにリフレクションを使わないコードとなっていました。
まだまだ出たばかりでこれからのフレームワークですが、起動が高速になるメリットはクラウドアプリケーション、FaaSで大きなアドバンテージになるでしょう。