MicronautはどうやってNamedアノテーションでBeanを取得してるんだろう?

最近、なんとなくMicronautを触り始めてる。

micronaut.io

## 気になった

順番にドキュメントを読んでいってて、この部分が気になった。

https://docs.micronaut.io/latest/guide/index.html#qualifiers

import javax.inject.*

interface Engine { 
    int getCylinders()
    String start()
}

@Singleton
class V6Engine implements Engine { 
    int cylinders = 6

    String start() {
        "Starting V6"
    }
}

@Singleton
class V8Engine implements Engine { 
    int cylinders = 8

    String start() {
        "Starting V8"
    }
}

@Singleton
class Vehicle {
    final Engine engine

    @Inject Vehicle(@Named('v8') Engine engine) { 
        this.engine = engine
    }

    String start() {
        engine.start() 
    }
}

Engineの実装として、V6EngineV8Engineが定義されていて、@Namedアノテーションで名前を指定してInjectできるよ、という部分。

この「名前」の部分、どういう動きでv8だけで取得できるんだろう?というのが気になった。v8engineだったらクラス名かなーと思うんだけど。

## とりあえず動かす

サンプルコードはGroovyかな?なので、セミコロンを後ろにつけたりして、手元にあったHelloControllerコンストラクターインジェクションで差し込んでみた。

package hello.world;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import javax.inject.*;

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

  final Engine engine;

  HelloController(@Named("v8") Engine engine){
    this.engine = engine;
  }

  @Get(produces = MediaType.TEXT_PLAIN)
  public String index() {
    return "Hello World. " + engine.start();
  }
}

interface Engine {
  int getCylinders();
  String start();
}

@Singleton
class V6Engine implements Engine {
  int cylinders = 6;

  @Override
  public int getCylinders() {
    return cylinders;
  }

  @Override
  public String start() {
    return "Starting V6";
  }
}

@Singleton
class V8Engine implements Engine {
  int cylinders = 8;

  @Override
  public int getCylinders() {
    return cylinders;
  }

  @Override
  public String start() {
    return "Starting V8";
  }
}
  • 確かにv8だけで動く
  • v6を指定するとV6Engineになる
  • v8engineでも動く
  • 前方一致なのかな?と思ってv8engにしてみたら動かない

ほほー。

## 実装クラスに名前を指定

@Namedアノテーションをつけると実装クラスに名前をつけることができる。

@Singleton
@Named("sss")
class V8Engine implements Engine {
  int cylinders = 8;
  • sssでInjectできる
  • v8ではInjectできなくなった
  • v8engineでもInjectできなくなった

なるほどー。

## ということで実装を探した

ここだな。

https://github.com/micronaut-projects/micronaut-core/blob/afc3fb377fea37f942c1a251be19b58f9f989b22/inject/src/main/java/io/micronaut/inject/qualifiers/NameQualifier.java#L60-L72

String typeName;
  AnnotationMetadata annotationMetadata = candidate.getAnnotationMetadata();
  // here we resolved the declared Qualifier of the bean
  Optional<String> beanQualifier = annotationMetadata.findDeclaredAnnotation(Named.class).flatMap(namedAnnotationValue -> namedAnnotationValue.getValue(String.class));
  typeName = beanQualifier.orElseGet(() -> {
    if (candidate instanceof NameResolver) {
      Optional<String> resolvedName = ((NameResolver) candidate).resolveName();
      return resolvedName.orElse(candidate.getBeanType().getSimpleName());
    }
    return candidate.getBeanType().getSimpleName();
  });
  return typeName.equalsIgnoreCase(name) || typeName.equalsIgnoreCase(name + beanType.getSimpleName());
}
  • 実装クラスに@Namedがついてる場合は
    • 1-1. 指定した名前が、@Namedvalueと一致すればOK
  • 実装クラスに@Namedがついていない場合は
    • 2-1. 指定した名前が、大文字小文字を無視して実装クラスのSimpleNameと一致すればOK
    • 2-2. 指定した名前 + BeanTypeのSimpleNameが、大文字小文字を無視して実装クラスのSimpleNameと一致すればOK

そっかー。だからv8だけを指定した場合は、2-2で、v8Engineインターフェース名がくっついたものと一致してるってことか。

すっきり。