使用 Google 开源库 AutoService 进行组件化开发,移动应用开发课程设计心得
import java.util.ServiceLoader;
public class DisplayFactory {private static DisplayFactory mDisplayFactory;
private Iterator<Display> mIterator;
private DisplayFactory() {ServiceLoader<Display> loader = ServiceLoader.load(Display.class);mIterator = loader.iterator();}
public static DisplayFactory getSingleton() {if (null == mDisplayFactory) {synchronized (DisplayFactory.class) {if (null == mDisplayFactory) {mDisplayFactory = new DisplayFactory();}}}return mDisplayFactory;}
public Display getDisplay() {return mIterator.next();}
public boolean hasNextDisplay() {return mIterator.hasNext();}}
使用就是这么几个步骤,比较简单,下面看下 AutoService 实现原理。
2.实现原理
首先先简单介绍下 Javac 的编译过程,大致可以分为 3 个过程:
解析与填充符号表
插入式注解处理器的注解处理过程
分析与字节码生成过程
看下一个图片,图片来源深入理解 Java 虚拟机,首先会进行词法和语法分析,词法分析将源代码的字符流转变为 Token 集合,关键字/变量名/字面量/运算符读可以成为 Token,词法分析过程由 com.sun.tools.javac.parserScanner 类实现;
语法分析是根据 Token 序列构造抽象语法树的过程,抽象语法树 AST 是一种用来描述程序代码语法结构的树形表示,语法树的每一个节点读代表着程序代码中的一个语法结构,例如包/类型/修饰符/运算符/接口/返回值/代码注释等,在 javac 的源码中,语法分析是由 com.sun.tools.javac.parser.Parser 类实现,这个阶段产出的抽象语法树由 com.sun.tools.javac.tree.JCTree 类表示。经过上面两个步骤编译器就基本不会再对源码文件进行操作了,后续的操作读建立在抽象语法树上。
完成了语法和词法分析后就是填充符号表的过程。符号表是由一组符号地址和符号信息构成的表格。填充符号表的过程由 com.sun.tools.javac.comp.Enter 类实现。
如前面介绍的,如果注解处理器在处理注解期间对语法树进行了修改,编译器将回到解析与填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个 Round,如下图中的环。
上面简单回顾了下编译注解的一些东西,接下来看下 AutoService 这个注解的实现,使用它有三个限定条件;
不能是内部类和匿名类,必须要有确定的名称
必须要有公共的,可调用的无参构造函数
使用这个注解的类必须要实现 value 参数定义的接口
@Documented@Target(TYPE)public @interface AutoService {/** Returns the interface implemented by this service provider. */Class<?> value();}
有注解,必须要有对应的注解处理器,AutoServiceProcessor
继承AbstractProcessor
,一般我们会实现其中的 3 个方法, 在getSupportedAnnotationTypes
中返回了支持的注解类型AutoService.class
;getSupportedSourceVersion
,用来指定支持的 java 版本,一般来说我们都是支持到最新版本,因此直接返回 SourceVersion.latestSupported()即可;主要还是 process 方法。
public class AutoServiceProcessor extends AbstractProcessor {
@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}
@Overridepublic Set<String> getSupportedAnnotationTypes() {return ImmutableSet.of(AutoService.class.getName());}
@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {try {return processImpl(annotations, roundEnv);} catch (Exception e) {// We don't allow exceptions of any kind to propagate to the compilerStringWriter writer = new StringWriter();e.printStackTrace(new PrintWriter(writer));fatalError(writer.toString());return true;}}}
process 方法调用processImpl
,接着看下这个方法的实现,先看下方法实现,就两个逻辑判断,如果上一次循环中注解处理器已经处理完了,就调用generateConfigFiles
生成 MEATA_INF 配置文件;如果上一轮没有处理就调用processAnnotations
处理注解。返回 true 就代表改变或者生成语法树中的内容;返回 false 就是没有修改或者生成,通知编译器这个 Round 中的代码未发生变化。
private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {if (roundEnv.processingOver()) {generateConfigFiles();} else {processAnnotations(annotations, roundEnv);}
return true;}
再接着往下看代码之前先看下两个环境变量,RoundEnvironment
和ProcessingEnvironment
。
RoundEnvironment
提供了访问到当前这个 Round 中语法树节点的功能,每个语法树节点在这里表示为一个 Element,在 javax.lang.model 包中定义了 16 类 Element,包括常用的元素:包,枚举,类,注解,接口,枚举值,字段,参数,本地变量,异常,方法,构造函数,静态语句块即 static{}块,实例语句块即{}块,参数化类型即反省尖括号内的类型,还有未定义的其他语法树节点。
public enum ElementKind {PACKAGE,ENUM,CLASS,ANNOTATION_TYPE,INTERFACE,ENUM_CONSTANT,FIELD,PARAMETER,LOCAL_VARIABLE,EXCEPTION_PARAMETER,METHOD,CONSTRUCTOR,STATIC_INIT,INSTANCE_INIT,TYPE_PARAMETER,OTHER,RESOURCE_VARIABLE;
private ElementKind() {}
public boolean isClass() {return this == CLASS || this == ENUM;}
public boolean isInterface() {return this == INTERFACE || this == ANNOTATION_TYPE;}
public boolean isField() {return this == FIELD || this == ENUM_CONSTANT;}}
看下RoundEnvironment
的源码,errorRaised
方法返回上一轮注解处理器是否产生错误;getRootElements
返回上一轮注解处理器生成的根元素;最后两个方法返回包含指定注解类型的元素的集合,画重点,这个就是我们自定义注解处理器需要经常打交道的方法。
public interface RoundEnvironment {boolean processingOver();
boolean errorRaised();
Set<? extends Element> getRootElements();
Set<? extends Element> getElementsAnnotatedWith(TypeElement var1);
Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> var1);}
另外一个参数ProcessingEnvironment
,在注解处理器初始化的时候(init()方法执行的时候)创建,代表了注解处理器框架提供的一个上下文环境,要创建新的代码或者向编译器输出信息或者获取其他工具类等都需要用到这个实例变量。看下它的源码。
Messager
用来报告错误,警告和其他提示信息;Filer
用来创建新的源文件,class 文件以及辅助文件;Elements
中包含用于操作 Element 的工具方法;Types
中包含用于操作类型 TypeMirror 的工具方法;
public interface ProcessingEnvironment {Map<String, String> getOptions();
Messager getMessager();
Filer getFiler();
Elements getElementUtils();
Types getTypeUtils();
SourceVersion getSourceVersion();
Locale getLocale();}
介绍完一些基础变量后,我们就接着上面先看下processAnnotations
方法,方法看起来有点长,但是结构很简单,首先第一步通过RoundEnvironment
的getElementsAnnotatedWith(AutoService.class)
拿到所有的标注了AutoService
注解的元素。
private void processAnnotations(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv) {
// 1.Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
log(annotations.toString());log(elements.toString());
for (Element e : elements) {// TODO(gak): check for error trees?// 2.TypeElement providerImplementer = (TypeElement) e;// 3.AnnotationMirror providerAnnotation = getAnnotationMirror(e, AutoService.class).get();// 4.DeclaredType providerInterface = getProviderInterface(providerAnnotation);TypeElement providerType = (TypeElement) providerInterface.asElement();
log("provider interface: " + providerType.getQualifiedName());log("provider implementer: " + providerImplementer.getQualifiedName());
// 5.if (!checkImplementer(providerImplementer, providerType)) {String message = "ServiceProviders must implement their service provider interface. "
providerImplementer.getQualifiedName() + " does not implement "
providerType.getQualifiedName();error(message, e, providerAnnotation);}
// 6.String providerTypeName = getBinaryName(providerType);String providerImplementerName = getBinaryName(providerImplementer);log("provider interface binary name: " + providerTypeName);log("provider implementer binary name: " + providerImplementerName);
providers.put(providerTypeName, providerImplementerName);}}
public static Optional<AnnotationMirror> getAnnotationMirror(Element element,Class<? extends Annotation> annotationClass) {String annotationClassName = annotationClass.getCanonicalName();for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {TypeElement annotationTypeElement = asType(annotationMirror.getAnnotationType().asElement());if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) {return Optional.of(annotationMirror);}}return Optional.absent();}
AutoService
只能作用于非内部非匿名类或者接口,第二步在 for 循环中强转 Element 为 TypeElement,这个就是被AutoService
标注的元素,这里简称为 T。接下来这个可能让人容易乱,在前面说过每一个 javac 是一个循环过程,在第一次扫描到AutoService
注解的时候是还没有 T 的 class 对象,所以也就不能通过反射来拿到这个注解和注解的参数值 value。这个时候第三步就得通过AnnotationMirror
,用来表示一个注解,通过它可以拿到注解类型和注解参数。在getAnnotationMirror
会判断这个 T 的注解(通过element.getAnnotationMirrors()
)名称是不是等于AutoService
,相等就返回这个AutoService
的AnnotationMirror
。
public interface AnnotationMirror {
评论