深入Dagger:自定义AutoValue

yanzhitao 8年前
   <h3><strong>前言</strong></h3>    <p>本篇文章介绍一下 AutoValue 的原理,并模仿自定义实现一个AutoValue。</p>    <p>AutoValue的是Google为了实现 ValueClass 设计的自动编译框架,具体的介绍可以参考Google的官方 说明 。</p>    <p>Dagger内部也大量使用了AutoValue的功能,来实现 <em>ValueClass</em> 。</p>    <h3><strong>AutoValue</strong></h3>    <p>AutoValue嵌入到JavaClass的编译过程,读取被注解的类,来创建一个新的ValueClass。这里有一个完整使用的 例子 。</p>    <p>这里主要介绍一下AutoValue的实现。</p>    <ol>     <li>定义注解 <em>AutoValue</em> 。 <pre>  <code class="language-python">@Retention(RetentionPolicy.SOURCE)   @Target(ElementType.TYPE)   public @interface AutoValue {   }</code></pre> </li>     <li>注册 <em>processor</em> ,AutoValue的jar包中的 <em>META-INF/services</em> 路径里面包含文件 <em>javax.annotation.processing.Processor</em> ,文件里包含了注册的 <em>processor</em> ,换行分割。这里面注册了 <em>AutoValueProcessor</em> 。</li>     <li><em>AutoValueProcessor</em> 的 <em>process</em> 方法实现了主要的处理逻辑,读取注释的类的信息,构造新的类,并写入文件。 <em>processType</em> 方法是处理单个类的方法,主要的逻辑如下 <pre>  <code class="language-python">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);</code></pre> <em>AutoValueTemplateVars</em> 保存了新的类的信息,并根据对应的模板生成源文件字符串. <pre>  <code class="language-python">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);       }   }</code></pre> <em>writeSourceFile</em> 则会根据原生api将源代码写入本地文件。</li>    </ol>    <h3><strong>MyAutoValue</strong></h3>    <p>所以自定义 <em>AutoValue</em> 也是类似的原理。这里构造 <em>MyAutoValue</em> 来读取注解的类,生成新的带有get,set和toString方法类。</p>    <p>因为 <em>processor</em> 的注册只能在jar中使用,不能跟源文件放在一起,所以这里新建了一个 <a href="/misc/goto?guid=4959727477105197107" rel="nofollow,noindex">工程</a> 来实现 <em>MyAutoValue</em> ,使用方法在 <a href="/misc/goto?guid=4959727477228793105" rel="nofollow,noindex">这里</a> 。</p>    <ol>     <li>定义 <em>MyAutoValue</em> 。 <pre>  <code class="language-python">@Retention(RetentionPolicy.SOURCE)  @Target(ElementType.TYPE)  public @interface MyAutoValue {  }</code></pre> </li>     <li>MyAutoValueProcessor 。同样先在 resources/META-INF/services 下新建 javax.annotation.processing.Processor ,并注册 MyAutoValueProcessor 。<br> MyAutoValueProcessor 继承了 AbstractProcessor ,并在 process 中实现了主要的逻辑。 <pre>  <code class="language-python">@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;   }</code></pre> 这里去取了所有被 MyAutoValue 注释的类,并交给processType去处理。 <pre>  <code class="language-python">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());       }   }</code></pre> processType 会读取类的字段生成一个新的*_MyAutoValue的类,并根据原有类的字段生成get,set和toString方法,然后将新类写入到本地文件中。 <pre>  <code class="language-python">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));   }</code></pre> makeFieldAndMethod 就是具体的构造字段和方法的逻辑,内部使用JavaPoet实现的,可以参考完整代码和上一篇文章,这里就不列出了。</li>     <li>打包编译,需要注意的 META-INF/services 的 javax.annotation.processing.Processor 会阻止javac的编译,打完包会发现里面没有class文件,所以需要加上特殊的参数。 <pre>  <code class="language-python"><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></code></pre> 加上 <em><compilerArgument>-proc:none</compilerArgument></em> 就可以实现完整的打包了。</li>     <li>使用 <em>MyAutoValue</em> 。在 <em>MyAutoValueClassTest</em> 类上注解 <em>MyAutoValue</em> 。 <pre>  <code class="language-python">@MyAutoValue   public class MyAutoValueClassTest {       private String a;       private String b;       private int c;   }</code></pre> 编译完后就会生成以下的新类,会发现自定带上了get,set和toString的方法。 <pre>  <code class="language-python">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 + "\"}";   }  }</code></pre> </li>    </ol>    <h3><strong>结语</strong></h3>    <p>dagger的实现跟AutoValue类似,也是根据注解嵌入编译实现新的类,只是AutoValue的逻辑比较简单,只是实现ValueClass的构造,dagger会涉及到更多依赖注入的功能。后面会介绍更多dagger的内容。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/8e530d51cf39</p>    <p> </p>