深入Dagger:自定義AutoValue
前言
本篇文章介紹一下 AutoValue 的原理,并模仿自定義實現一個AutoValue。
AutoValue的是Google為了實現 ValueClass 設計的自動編譯框架,具體的介紹可以參考Google的官方 說明 。
Dagger內部也大量使用了AutoValue的功能,來實現 ValueClass 。
AutoValue
AutoValue嵌入到JavaClass的編譯過程,讀取被注解的類,來創建一個新的ValueClass。這里有一個完整使用的 例子 。
這里主要介紹一下AutoValue的實現。
- 定義注解 AutoValue 。
@Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface AutoValue { }
- 注冊 processor ,AutoValue的jar包中的 META-INF/services 路徑里面包含文件 javax.annotation.processing.Processor ,文件里包含了注冊的 processor ,換行分割。這里面注冊了 AutoValueProcessor 。
- AutoValueProcessor 的 process 方法實現了主要的處理邏輯,讀取注釋的類的信息,構造新的類,并寫入文件。 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);
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); } }
MyAutoValue
所以自定義 AutoValue 也是類似的原理。這里構造 MyAutoValue 來讀取注解的類,生成新的帶有get,set和toString方法類。
因為 processor 的注冊只能在jar中使用,不能跟源文件放在一起,所以這里新建了一個 工程 來實現 MyAutoValue ,使用方法在 這里 。
- 定義 MyAutoValue 。
@Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) public @interface MyAutoValue { }
- 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; }
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()); } }
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)); }
- 打包編譯,需要注意的 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>
- 使用 MyAutoValue 。在 MyAutoValueClassTest 類上注解 MyAutoValue 。
@MyAutoValue public class MyAutoValueClassTest { private String a; private String b; private int c; }
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 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!