DevNexus 2019で、Micronautのセッションを聴いてきたので、メモがてら断片的に記します。
Micronautの特徴の中に、こういったものがあります。
- Minimal use of reflection
- Minimal use of proxies
リフレクションや動的プロキシの利用を最低限にして、起動速度の向上やフットプリントの削減を図っています。Micornautでは、リフレクションを使わずにBeanのIntrospectionができます。単純なBookクラスでの、Introspectionを例にします。
import io.micronaut.core.annotation.Introspected; @Introspected public class Book { private String isbn; private String name; public Book(String isbn, String name) { this.isbn = isbn; this.name = name; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
クラスに、io.micronaut.core.annotation.Introspected
アノテーションをつけました。@Introspected
があるクラスは、io.micronaut.core.beans.BeanIntrospection
から扱えます。例として、newを使わずにインスタンスを作ります。
final BeanIntrospection<Book> introspection = BeanIntrospection.getIntrospection(Book.class); Book b = introspection.instantiate("3", "ccc");
このコードは、リフレクションを使っていないのです。@Introspected
を外すと、実行時例外となります。
// @Introspected public class Book {
Unexpected error occurred: No bean introspection available for type [class example.micronaut.Book]. Ensure the class is annotated with io.micronaut.core.annotation.Introspected io.micronaut.core.beans.exceptions.IntrospectionException: No bean introspection available for type [class example.micronaut.Book]. Ensure the class is annotated with io.micronaut.core.annotation.Introspected
Ensure the class is annotated with io.micronaut.core.annotation.Introspected
とあります。
Micornautは、Ahead of Time (AOT) コンパイル/事前コンパイルとして、アノテーションプロセッサで@Introspected
があるクラスから、BeanIntrospection
で必要となるクラスやコードを自動生成しているのです。
$ tree target/classes/example/micronaut/ target/classes/example/micronaut/ ├── $Book$Introspection$$0.class ├── $Book$Introspection$$1.class ├── $Book$Introspection.class ├── $Book$IntrospectionRef$$AnnotationMetadata.class ├── $Book$IntrospectionRef.class ...
$Introspectionとついたクラスが生成されています。$$0、$$1はBookクラスのフィールドです。たとえば、Bookクラスにpriceフィールドを足してみます。
@Introspected public class Book { private String isbn; private String name; private int price; // getter and setter
すると、出力が増えます。
$ tree target/classes/example/micronaut/ target/classes/example/micronaut/ ├── $Book$Introspection$$0.class ├── $Book$Introspection$$1.class ├── $Book$Introspection$$2.class ├── $Book$Introspection.class ├── $Book$IntrospectionRef$$AnnotationMetadata.class ├── $Book$IntrospectionRef.class ...
これらのクラスは、デコンパイラでは読めません(IntelliJ、JDなど)。
public final class $Book$Introspection extends io.micronaut.core.beans.AbstractBeanIntrospection { public $Book$Introspection() { /* compiled code */ } public java.lang.Object instantiate() { /* compiled code */ } public io.micronaut.core.type.Argument[] getConstructorArguments() { /* compiled code */ } public java.lang.Object instantiateInternal(java.lang.Object[] objects) { /* compiled code */ } }
javapもエラーになります。
$ javap example.micronaut.$Book$Introspection エラー: クラスが見つかりません: example.micronaut. $ javap example.micronaut.$Book$Introspection$$0 エラー: クラスが見つかりません: example.micronaut.727860
コードを生成している、アノテーションプロセッサを見てみます。Mavenプロジェクトにしましたので、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>
2つありますが、Introspectionはmicronaut-inject-javaの方です。
io.micronaut.annotation.processing.TypeElementVisitorProcessor
が、アノテーションプロセッサ本体です。
/** * <p>The annotation processed used to execute type element visitors.</p> * * @author James Kleeh * @author graemerocher * @since 1.0 */ @SupportedAnnotationTypes("*") public class TypeElementVisitorProcessor extends AbstractInjectAnnotationProcessor { ... @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { ... Collection<TypeElementVisitor> typeElementVisitors = findTypeElementVisitors(); ... for (LoadedVisitor loadedVisitor : loadedVisitors) { try { loadedVisitor.getVisitor().start(javaVisitorContext); } catch (Throwable e) { error("Error initializing type visitor [%s]: %s", loadedVisitor.getVisitor(), e.getMessage()); } } ... } protected @Nonnull Collection<TypeElementVisitor> findTypeElementVisitors() { Map<String, TypeElementVisitor> typeElementVisitors = new HashMap<>(10); SoftServiceLoader<TypeElementVisitor> serviceLoader = SoftServiceLoader.load(TypeElementVisitor.class, getClass().getClassLoader()); ...
io.micronaut.inject.visitor.TypeElementVisitorを実装するクラスを探して、ループでvisitしてる感じです。
TypeElementVisitorの実装クラスの1つに、io.micronaut.inject.beans.visitor.IntrospectedTypeElementVisitorがあり、このクラスが@Introspected
を処理しています。
/** * A {@link TypeElementVisitor} that visits classes annotated with {@link Introspected} and produces * {@link io.micronaut.core.beans.BeanIntrospectionReference} instances at compilation time. * * @author graemerocher * @since 1.1 */ @Internal public class IntrospectedTypeElementVisitor implements TypeElementVisitor<Introspected, Object> { @Override public void visitClass(ClassElement element, VisitorContext context) { final AnnotationValue<Introspected> introspected = element.getAnnotation(Introspected.class); ...
最終的には、io.micronaut.inject.beans.visitor.BeanIntrospectionWriterで、ASMを使ってクラスを出力しています。
こういった処理をコンパイル時に実行するため、Micronautでは起動が早くなる代わりに、ビルド時間が長くなっています。