深入Dagger:自定義AutoValue

yanzhitao 8年前發布 | 9K 次閱讀 Dagger Python開發

前言

本篇文章介紹一下 AutoValue 的原理,并模仿自定義實現一個AutoValue。

AutoValue的是Google為了實現 ValueClass 設計的自動編譯框架,具體的介紹可以參考Google的官方 說明 。

Dagger內部也大量使用了AutoValue的功能,來實現 ValueClass

AutoValue

AutoValue嵌入到JavaClass的編譯過程,讀取被注解的類,來創建一個新的ValueClass。這里有一個完整使用的 例子 。

這里主要介紹一下AutoValue的實現。

  1. 定義注解 AutoValue
    @Retention(RetentionPolicy.SOURCE)
     @Target(ElementType.TYPE)
     public @interface AutoValue {
     }
  2. 注冊 processor ,AutoValue的jar包中的 META-INF/services 路徑里面包含文件 javax.annotation.processing.Processor ,文件里包含了注冊的 processor ,換行分割。這里面注冊了 AutoValueProcessor
  3. AutoValueProcessorprocess 方法實現了主要的處理邏輯,讀取注釋的類的信息,構造新的類,并寫入文件。 processType 方法是處理單個類的方法,主要的邏輯如下
    AutoValueTemplateVars vars = new AutoValueTemplateVars();
     vars.pkg = TypeSimplifier.packageNameOf(type);
     vars.origClass = TypeSimplifier.classNameOf(type);
     vars.simpleClassName = TypeSimplifier.simpleNameOf(vars.origClass);
     vars.subclass = TypeSimplifier.simpleNameOf(subclass);
     vars.finalSubclass = TypeSimplifier.simpleNameOf(finalSubclass);
     vars.isFinal = applicableExtensions.isEmpty();
     vars.types = processingEnv.getTypeUtils();
     determineObjectMethodsToGenerate(methods, vars);
     defineVarsForType(type, vars, toBuilderMethods, propertyMethods, builder);
     GwtCompatibility gwtCompatibility = new GwtCompatibility(type);
     vars.gwtCompatibleAnnotation = gwtCompatibility.gwtCompatibleAnnotationString();
     String text = vars.toText();
     text = Reformatter.fixup(text);
     writeSourceFile(subclass, text, type);
    AutoValueTemplateVars 保存了新的類的信息,并根據對應的模板生成源文件字符串.
    private void writeSourceFile(String className, String text, TypeElement originatingType) {
         try {
           JavaFileObject sourceFile =
               processingEnv.getFiler().createSourceFile(className, originatingType);
           Writer writer = sourceFile.openWriter();
           try {
             writer.write(text);
           } finally {
             writer.close();
           }
         } catch (IOException e) {
           processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
               "Could not write generated class " + className + ": " + e);
         }
     }
    writeSourceFile 則會根據原生api將源代碼寫入本地文件。

MyAutoValue

所以自定義 AutoValue 也是類似的原理。這里構造 MyAutoValue 來讀取注解的類,生成新的帶有get,set和toString方法類。

因為 processor 的注冊只能在jar中使用,不能跟源文件放在一起,所以這里新建了一個 工程 來實現 MyAutoValue ,使用方法在 這里

  1. 定義 MyAutoValue
    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.TYPE)
    public @interface MyAutoValue {
    }
  2. MyAutoValueProcessor 。同樣先在 resources/META-INF/services 下新建 javax.annotation.processing.Processor ,并注冊 MyAutoValueProcessor 。
    MyAutoValueProcessor 繼承了 AbstractProcessor ,并在 process 中實現了主要的邏輯。
    @Override
     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
         Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(MyAutoValue.class);
         if (elements == null || elements.isEmpty()) {
             return true;
         }
         for (Element element : elements) {
             if (!(element instanceof TypeElement)) {
                 continue;
             }
             try {
                 processType(element);
             } catch (Exception e) {
                 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), element);
             }
         }
         return true;
     }
    這里去取了所有被 MyAutoValue 注釋的類,并交給processType去處理。
    private void processType(Element element) {
         TypeElement typeElement = (TypeElement) element;
         String className = element.getSimpleName() + "_MyAutoValue";
         TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(className);
         typeSpecBuilder.addAnnotation(makeAnnotationSpec());
         typeSpecBuilder.addModifiers(Modifier.PUBLIC);
         String packageName = getPackageName(typeElement);
         try {
             makeFieldAndMethod(typeElement, typeSpecBuilder);
         } catch (ClassNotFoundException e) {
             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
         }
         JavaFile.Builder javaFileBuilder = JavaFile.builder(packageName, typeSpecBuilder.build());
         String text = javaFileBuilder.build().toString();
         try {
             JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(className, element);
             Writer writer = sourceFile.openWriter();
             try {
                 writer.write(text);
             } finally {
                 writer.close();
             }
         } catch (IOException e) {
             processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
         }
     }
    processType 會讀取類的字段生成一個新的*_MyAutoValue的類,并根據原有類的字段生成get,set和toString方法,然后將新類寫入到本地文件中。
    private void makeFieldAndMethod(Element element, TypeSpec.Builder typeSpecBuilder) throws ClassNotFoundException {
         List<VariableElement> elementList = ElementFilter.fieldsIn(element.getEnclosedElements());
         if (elementList == null || elementList.isEmpty()) {
             return;
         }
         List<String> fieldList = new ArrayList<>(elementList.size());
         for (VariableElement variableElement : elementList) {
             String fieldName = variableElement.getSimpleName().toString();
             fieldList.add(fieldName);
             TypeName typeName = TypeName.get(variableElement.asType());
             typeSpecBuilder.addField(makeFieldSpec(fieldName, typeName));
             typeSpecBuilder.addMethod(makeSetMethod(fieldName, typeName));
             typeSpecBuilder.addMethod(makeGetMethod(fieldName, typeName));
         }
         typeSpecBuilder.addMethod(makeToStringMethod(fieldList));
     }
    makeFieldAndMethod 就是具體的構造字段和方法的邏輯,內部使用JavaPoet實現的,可以參考完整代碼和上一篇文章,這里就不列出了。
  3. 打包編譯,需要注意的 META-INF/services 的 javax.annotation.processing.Processor 會阻止javac的編譯,打完包會發現里面沒有class文件,所以需要加上特殊的參數。
    <build>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>2.3.2</version>
                 <configuration>
                     <source>1.7</source>
                     <target>1.7</target>
                     <encoding>UTF-8</encoding>
                     <compilerArgument>-proc:none</compilerArgument>
                 </configuration>
             </plugin>
         </plugins>
     </build>
    加上 <compilerArgument>-proc:none</compilerArgument> 就可以實現完整的打包了。
  4. 使用 MyAutoValue 。在 MyAutoValueClassTest 類上注解 MyAutoValue
    @MyAutoValue
     public class MyAutoValueClassTest {
         private String a;
         private String b;
         private int c;
     }
    編譯完后就會生成以下的新類,會發現自定帶上了get,set和toString的方法。
    public class MyAutoValueClassTest_MyAutoValue {
     private String a;
     private String b;
     private int c;
     public MyAutoValueClassTest_MyAutoValue() {
     }
     public void setA(String a) {
         this.a = a;
     }
     public String getA() {
         return this.a;
     }
     public void setB(String b) {
         this.b = b;
     }
     public String getB() {
         return this.b;
     }
     public void setC(int c) {
         this.c = c;
     }
     public int getC() {
         return this.c;
     }
     public String toString() {
         return "{\"a\":\"" + this.a + "\",\"b\":\"" + this.b + "\",\"c\":\"" + this.c + "\"}";
     }
    }

結語

dagger的實現跟AutoValue類似,也是根據注解嵌入編譯實現新的類,只是AutoValue的邏輯比較簡單,只是實現ValueClass的構造,dagger會涉及到更多依賴注入的功能。后面會介紹更多dagger的內容。

 

來自:http://www.jianshu.com/p/8e530d51cf39

 

 本文由用戶 yanzhitao 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!