はじめに
先日、Micronaut 1.0がリリースされました。
Micronautは、起動が高速なフルスタックフレームワークと言われています。
SpringのようなリフレクションベースのIoCコンテナではなく、コンパイル時に依存関係を解決することで、高速に起動できるとのこと。
実際にどれだけ高速になるかは別の記事を参考にするとして、本記事では、コンパイル時にどのように依存解決しているのか、逆コンパイルをして確かめてみようと思います。
準備
ドキュメントに従ってプロジェクトを作成します。
ここではMavenプロジェクトを作成します。
mn create-app --build maven hello-world
maven-compiler-pluginのconfigurationに、micronautのプラグインがアノテーションプロセッサとして登録されていました。
<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"
を返すだけの単純なプログラムです。
package hello.world;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String... args) {
Micronaut.run(Application.class);
}
}
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();
}
}
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を見てみましょう。
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
にあります。
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で大きなアドバンテージになるでしょう。