在JDK 1.5以后,java提供了对注解的支持,这些注解与普通代码同样,在运行期间发挥做用。在JDK 1.6中实现了JSR-269规范,提供了一组插入式注解处理器的标准API在编译期间对注解进行处理,能够看做是一组编译器的插件,能够读取/修改/添加抽象语法树中的任意元素。java
在Android模块开发之APT技术介绍了自定义注解处理器的一些知识,自定义注解处理器注册才能被Java虚拟机调用,在上面的博客第四小节中用的方法是手动注册,这比较违反程序员懒的特色,在里面也提到了自动注册的方法,就是AutoService,今天这篇博客就是来扒一扒谷歌提供的这个开源库。git
先经过一个栗子看下AutoService怎么用的。程序员
定义一个简单的接口:github
public interface Display {
String display();
}
复制代码
有两个Module A和B分别实现了这个接口,而后在app Module中调用这两个实现类, 比较低级的办法就是在app Module中直接依赖这两个模块,而后就能够调用实现类了。这有两个坏处,一个是app Module直接强依赖A和B两个Module,另外若是开发中拿不到依赖的模块呢,有可能模块是第三方的,这个时候强依赖这种方式就行不通了。bash
看下AutoService是怎么实现的,先看下包结构,interfaces只简单包含上面的Display接口,modulea和moduleb实现这个接口,app统一加载全部这个接口的实现类。 app
看下modulea和moduleb实现,方法实现里面简单返回一个字符串,主要是上面的@AutoService(Display.class)注解,注解值是接口的名称,也就是implements实现的类接口名称。框架
// modulea
import com.google.auto.service.AutoService;
@AutoService(Display.class)
public class ADisplay implements Display{
@Override
public String display() {
return "A Display";
}
}
// moduleb
@AutoService(Display.class)
public class BDisplay implements Display {
@Override
public String display() {
return "B Display";
}
}
复制代码
再看下app Module里面的怎么调用上面的ADispaly和BDisplay,加载原理就是经过ServiceLoader去加载,能够获得接口Display
的全部实现类,在咱们这个栗子中就是上面的ADisplay
和BDisplay
两个实现者。DisplayFactory
经过getDisplay
能够拿到全部的实现类。ide
import com.example.juexingzhe.interfaces.Display;
import java.util.Iterator;
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实现原理。函数
首先先简单介绍下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这个注解的实现,使用它有三个限定条件;
@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 {
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return ImmutableSet.of(AutoService.class.getName());
}
@Override
public 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 compiler StringWriter 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 {
DeclaredType getAnnotationType();
Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues();
}
复制代码
拿到这个注解了,接下来就是要拿到注解的参数value值了,这个在第四步getProviderInterface
方法中完成。
private DeclaredType getProviderInterface(AnnotationMirror providerAnnotation) {
Map<? extends ExecutableElement, ? extends AnnotationValue> valueIndex =
providerAnnotation.getElementValues();
log("annotation values: " + valueIndex);
AnnotationValue value = valueIndex.values().iterator().next();
return (DeclaredType) value.getValue();
}
复制代码
这里也是同上面的缘由,在这个阶段咱们不可能经过下面的代码反射来拿到注解的参数值,由于这个时候还拿不到class对象。因此上面费了很大的劲去经过AnnotationMirror
来拿到注解的参数值,在咱们这个栗子中就是Display.class
了。
AutoService autoservice = e.getAnnotation(AutoService.class);
Class<?> providerInterface = autoservice.value()
复制代码
接下来第5步检查类型T是否是实现了注解参数值说明的接口,也就是ADisplay
和BDisplay
是否是实现了Display
接口,没有实现确定就是没有意义了。第6步就是获取到接口名和实现类名,注册到map中,相似于Map<Display, [ADisplay, BDisplay]>这种形式,即key是接口名,value是实现了接口也就是注解AutoService
标注的实现类。
经过上面的步骤就已经扫描获得了全部的经过AutoService
标注的实现类和对应接口的映射关系,而且在processImpl
里面返回了true
,下个Round就是生成配置文件了。看下processImpl
if分支里面的generateConfigFiles
方法。
private void generateConfigFiles() {
Filer filer = processingEnv.getFiler();
// 1.
for (String providerInterface : providers.keySet()) {
String resourceFile = "META-INF/services/" + providerInterface;
log("Working on resource file: " + resourceFile);
try {
SortedSet<String> allServices = Sets.newTreeSet();
try {
// 2.
FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
log("Looking for existing resource file at " + existingFile.toUri());
// 3.
Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
log("Existing service entries: " + oldServices);
allServices.addAll(oldServices);
} catch (IOException e) {
// According to the javadoc, Filer.getResource throws an exception
// if the file doesn't already exist. In practice this doesn't
// appear to be the case. Filer.getResource will happily return a
// FileObject that refers to a non-existent file but will throw
// IOException if you try to open an input stream for it.
log("Resource file did not already exist.");
}
// 4.
Set<String> newServices = new HashSet<String>(providers.get(providerInterface));
if (allServices.containsAll(newServices)) {
log("No new service entries being added.");
return;
}
allServices.addAll(newServices);
log("New service file contents: " + allServices);
// 5.
FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
OutputStream out = fileObject.openOutputStream();
ServicesFiles.writeServiceFile(allServices, out);
out.close();
log("Wrote to: " + fileObject.toUri());
} catch (IOException e) {
fatalError("Unable to create " + resourceFile + ", " + e);
return;
}
}
}
复制代码
主要分红5个步骤要生成配置文件,分别来看下:
META-INF/services/com.example.juexingzhe.interfaces.Display
allServices
中processAnnotations
方法输出的映射map是否不存在上面的allServices
,不存在则添加,存在则直接返回不须要生成新的文件resourceFile
,文件内容就是allServices
中的全部实现类。最后咱们看下编译编译的结果,在每一个module
中都会生成配置文件
最后在apk中会合并全部的META-INF文件目录,能够看到在接口文件Display下面包含了全部module中经过AutoService
注解标注的实现类。
最后经过ServiceLoader
就能够经过反射拿到全部的实现类,ServiceLoader
的源码分析能够参考个人另一片博客Android模块开发之SPI.
此次的源码分析实际上是完成以前在Android模块开发之APT技术立下的flag,到今天才补上有点惭愧。AutoService源码解析结合APT和SPI服用效果可能更佳哦。
这个也是组件化技术中经常使用的一个技术点,后面会再更新一些组件化gradle相关的一些知识,有须要的小伙伴们欢迎关注。
完。
参考: 深刻理解Java虚拟机