Help us understand the problem. What is going on with this article?

Daggerはどうやってアノテーションからクラスを取り出しているのか

More than 3 years have passed since last update.

MirroredTypeException: Attempt to access Class object for TypeMirrorとか言われてブチギレてる人向け.


TL;DR

こんだけ.

public static List<TypeMirror> getClassesFromAnnotation(Element element, Class<? extends Annotation> annotationType, String argName) {
  AnnotationMirror am = getAnnotationMirror(element, annotationType).get();
  AnnotationValue av = getAnnotationValue(am, argName);
  return TO_LIST_OF_TYPE.visit(av);
}

private static final AnnotationValueVisitor<ImmutableList<TypeMirror>, Void> TO_LIST_OF_TYPE = new SimpleAnnotationValueVisitor6<ImmutableList<TypeMirror>, Void>() {
  @Override
  public ImmutableList<TypeMirror> visitArray(List<? extends AnnotationValue> vals, Void aVoid) {
    return FluentIterable.from(vals).transform(new Function<AnnotationValue, TypeMirror>() {
      @Override
      public TypeMirror apply(AnnotationValue input) {
        return TO_TYPE.visit(input);
      }
    }).toList();
  }
};

private static final AnnotationValueVisitor<TypeMirror, Void> TO_TYPE = new SimpleAnnotationValueVisitor6<TypeMirror, Void>() {
  @Override
  public TypeMirror visitType(TypeMirror t, Void aVoid) {
    return t;
  }
};

長く見えるのはクラス名がいちいち長いからとジェネリクスのせいです.
IDEの補完があれば手間なく打てます.

static importされてるのはcommonMoreElements.getAnnotationMirror()AnnotationMirrors.getAnnotationValue()です.

目的

たとえばDagger使うときに書くこんなの.

@Component(
  dependencies = {
    CocoaComponent.class,
    ChinoComponent.class,
    RizeComponent.class,
    ChiyaComponent.class,
    SyaroComponent.class
  }
)
public interface GochiusaComponent {
}

この@Componentに渡したdependenciesのクラスを取り出したい.

よくある事故

@Componentの定義は以下のようになっているはず(実際はもうちょい色々ある).

@Target(TYPE)
public @interface Component {
  Class<?>[] dependencies() default {};
  Class<?>[] modules() default {};
}

ここで,「じゃあふつうにgetAnnotation()してくればいいじゃん!」となる.

element.getAnnotation(Component.class).dependencies();

すると最初に挙げたエラーを吐いて死ぬことになる.

一応,以下のようにしてMirroredTypeExceptionをcatchしてあげればTypeMirrorが取り出せる.

try {
  element.getAnnotation(Component.class).dependencies();
} catch(MirroredTypeException e) {
  return e.getTypeMirror();
}

Exception吐く前提の対応.

google/daggerの場合

では,コード生成大好き企業ことGoogleさんちのDaggerはどうしているのか.
google/autoというJavaでコード生成して遊ぶためのサムシングが入ったリポジトリが存在しており,Dagger compilerはその中のcommon(Auto common utilities)というライブラリをフル活用して実装されている.

本稿では記事執筆時点での最新のmasterのコードを追いながら,Daggerが如何にしてアノテーションからクラスをぶっこ抜いているかを紹介する.また,google/autoについてはauto-common-0.5のタグを参考にする.

前提知識

commonにはBaseAnnotationProcessor)というクラスが存在する.これはAbstractProcessorを実装しており,処理をProcessingStepという単位に分割してくれる.Daggerでもこれを利用している.

ComponentProcessingStep

前述のとおり処理は複数のProcessingStepに分割されている.そのなかから今回の目的の処理をしてそうなStepであるComponentProcessingStepを見ていく.
…が,DaggerのProcessingStepはすべてAbstractComponentProcessingStepを継承しているので,まずはそこを見てみる.
とりあえずコンストラクタを見てみるが,フィールドの初期化しかしていない.ちょっとどこ見たらいいかわからなくなりそうだが,ここでオーバーライドされたprocess()メソッドを見てみる.BaseAnnotationProcessorが呼び出すための,実際の処理を記述していくメソッドである.

@Override
public final ImmutableSet<Element> process(
  SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
  // 省略
  for (Element element : elementsByAnnotation.get(componentAnnotation)) {
    TypeElement componentTypeElement = MoreElements.asType(element);
    try {
      if (componentElementValidator.validateComponent(componentTypeElement, messager)) {
        ComponentDescriptor componentDescriptor =
          componentDescriptorFactory.forComponent(componentTypeElement);
        // 省略
      }
    } catch (TypeNotPresentException e) {
      rejectedElements.add(componentTypeElement);
    }
  }
  return rejectedElements.build();
}

注目すべきはComponentDescriptor$Factory#forComponent()ComponentDescriptor…如何にもなネーミング.

ComponentDescriptor

ComponentDescriptor$Factory#forComponent()ComponentDescriptor$Factory#create()という非常に長いメソッドを呼び出す.このメソッドを読んでいくと以下のようなコードが見つかる.

private ComponentDescriptor create(TypeElement componentDefinitionType, Kind kind) {
  ImmutableSet.Builder<ModuleDescriptor> modules = ImmutableSet.builder();
  for (TypeMirror moduleIncludesType : getComponentModules(componentMirror)) {
    modules.add(moduleDescriptorFactory.create(MoreTypes.asTypeElement(moduleIncludesType)));
  }
}

getComponentModules()…とてもアヤシイ.

ConfigurationAnnotations.getComponentModules()

先ほどのgetComponentModules()はstatic importされており,呼ばれているのはConfigurationAnnotations.getComponentModules()

private static final String MODULES_ATTRIBUTE = "modules";

static ImmutableList<TypeMirror> getComponentModules(AnnotationMirror componentAnnotation) {
  checkNotNull(componentAnnotation);
  return convertClassArrayToListOfTypes(componentAnnotation, MODULES_ATTRIBUTE);
}

答えに近づいてきたような気がする.

ConfigurationAnnotations.convertClassArrayToListOtTypes()

まずConfigurationAnnotations.convertClassArrayToListOtTypes()ですが,これはVisitorにvisitしているだけ(Visitorパターンについて詳細はここでは説明しないので,知りたい方はJava言語で学ぶデザインパターン入門などを参照されたい).
Visitorの実装を読んでみる.
例外を投げるために丁寧に書かれているが,実体はここだけ.

FluentIterable.from(vals)
    .transform(
        new Function<AnnotationValue, TypeMirror>() {
          @Override
          public TypeMirror apply(AnnotationValue typeValue) {
            return TO_TYPE.visit(typeValue);
          }
    })
    .toList();

若干RxJavaっぽい(Streamっぽい)実装,Googleが出している便利ライブラリGuavaFluentIterable.ここでは別なVisitorにvisitすることで,AnnotationValueからTypeMirrorを取り出してリストにまとめている.
呼びだされた別のVisitorを見てみる.

@Override
public TypeMirror visitType(TypeMirror t, Void p) {
  return t;
}

…ふつうにTypeMirror返してるだけ.これがDaggerでアノテーションからクラスを取り出す処理の全貌である(?).

References

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away