目录
- AutoService的使用
- 关于SPI
- SPI示例
- APT技术
- AutoService源码
- AutoService源码分析
一般我们用它来自动帮我们注册APT文件(全称是Annotation Process Tool,或者叫注解处理器,AbstractProcessor的实现)。很多生成SPI文件的框架也是抄袭它的源码,可见它的作用还不小。
APT其实就是基于SPI一个工具,是JDK留给开发者的一个在编译前处理注解的接口。APT也是SPI的一个应用。关于SPI和APT下文会详细讲到。
先讲一下它是如何使用的。
AutoService的使用
AutoService框架的作用是自动生成SPI清单文件(META-INF/services下的文件)。不用它也行,如果不使用它就需要手动去创建这个文件、手动往这个文件里添加服务(接口实现)。
AutoService比较常用的场景是帮助注册APT(注解处理器)。下面以APT的例子来讲解它的使用。
开发APT需要在Java SE项目中开发,因为需要继承AbstractProcessor,AbstractProcessor作用在Java编译阶段。
先创建Java module,在Android Studio中也可以创建,然后在build.gradle中添加依赖,如下dependencies部分。
通过annotationProcessor添加注解处理器(AutoServiceProcessor.class),同时需要通过implementation添加annotation依赖,即AutoService.class。
plugins { | |
id 'java-library' | |
} | |
dependencies { | |
annotationProcessor 'com.google.auto.service:auto-service:1.0.1' | |
//一般结合JavaPoet框架来生成Java代码,这里不对它进行阐述。 | |
//implementation 'com.squareup:javapoet:1.13.0' | |
implementation 'com.google.auto.service:auto-service-annotations:1.0.1' | |
} |
然后在你处理注解处理器类上方添加@AutoService注解即可,value指定成javax.annotation.processing.Processor类,因为要生成的SPI清单文件(META-INF/services下的文件)名称是
javax.annotation.processing.Processor 这个Processor是Java内置的,Javac编译前默认的注解处理器接口。如果是我们自定义的接口就指定成自己的接口名。
Processor.class}) | (value = {|
public class MyProcessor extends AbstractProcessor { | |
public synchronized void init(ProcessingEnvironment processingEnv) { | |
System.out.println("MyProcessor------------init---------------"); | |
super.init(processingEnv); | |
} | |
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | |
System.out.println("MyProcessor------------process---------------"); | |
return false; | |
} | |
} |
AbstractProcessor是继承自Processor接口:
public abstract class AbstractProcessor implements Processor { | |
... | |
} |
AbstractProcessor这个类是JDK SE中的,Android Framework将它删除了(因为不需要也用不着),所以Android Module里面是不存在的。这也说明为什么创建Java SE项目来编写APT代码。
AutoService注解的声明如下,它的value是一个class集合,可以指定多个value。
@Documented | |
@Retention(CLASS) | |
@Target(TYPE) | |
public @interface AutoService { | |
/** Returns the interfaces implemented by this service provider. */ | |
Class<?>[] value(); | |
} |
以上示例中MyProcessor的作用是处理项目的自定义注解,比如Arouter框架会利用它来处理@Aouter注解,并自动生成路由注册类。
编译这个Java项目后就会自动将MyProcessor添加到APT的SPI注册文件中。
要注意的是,这个时候MyProcessor是没有起作用的,init和process方法都不会执行。因为注解处理阶段它并不在SPI注册文件中,注解处理阶段完成后它才注册进去。将Java项目打包成jar,这个MyProcessor才会在SPI注册文件中。别的项目依赖这个jar,MyProcessor的代码才会执行。
以上是AutoService的使用。讲了这些,可能有人看不懂。没关系,先了解一下SPI技术。
关于SPI
什么是SPI呢,了解SPI是读懂AutoService的基础。
SPI是Service Provider Interface的简称,是JDK默认提供的一种将接口和实现类进行分离的机制。这种机制能将接口和实现进行解耦,大大提升系统的可扩展性。
SPI机制约定:当一个Jar包需要提供一个接口的实现类时,这个Jar包需要在META-INF/services目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该Jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
SPI示例
比如有一个接口IMyService
package com.devnn.demo.interface | |
public interface IMyService { | |
void hello(); | |
} |
它的实现类有:
package com.devnn.demo.impl | |
import com.devnn.demo.interfaces.devnnService; | |
public class MyServiceImpl_ implements IMyService { | |
public void hello() { | |
System.out.println("Hi,I am MyServiceImpl_"); | |
} | |
} | |
package com.devnn.demo.impl; | |
import com.devnn.demo.interfaces.devnnService; | |
public class MyServiceImpl_ implements IMyService { | |
public void hello() { | |
System.out.println("Hi,I am MyServiceImpl_"); | |
} | |
} |
在resource/META-INF/services目录下创建文件com.devnn.demo.interface.IMyService,内容为所有实现类的完整名称:
com.devnn.demo.impl.MyServiceImpl_1
com.devnn.demo.impl.MyServiceImpl_2
项目结构:
加载IMyService接口的所有子类:
public class SPI_Demo { | |
public static void main(String[] agrs) { | |
//使用jdk提供的类ServiceLoader来加载IMyService的子类 | |
ServiceLoader<IMyService> loaders = ServiceLoader.load(IMyService.class); | |
//遍历并调用子类方法 | |
for (IMyService service : loaders) { | |
service.hello(); | |
} | |
} | |
} |
运行就会打印:
Hi,I am MyServiceImpl_1
Hi,I am MyServiceImpl_2
是不是很神奇,通过一个接口,就可以找到它的实现类,这就是SPI的作用。
APT技术
然后再说下APT,开头说了APT是SPI的一个应用。为什么这么说呢?APT其实就是Java给我们提供的内置的SPI接口,作用是在编译java前处理java源码中的注解。
APT的服务接口就是这个
javax.annotation.processing.Processor
跟META_INF/service下的文件名是一致的。
Java编译器读取这个清单文件,加载实现这个接口的所有类,完成用户的注解处理逻辑。
AutoService源码
然后再回到AutoService,结合源码对它进行剖析,AutoService主要代码就一个类,即AutoServiceProcessor.java,为了方便阅读,笔者先将它原封不动copy在这里,后面再对它进行解析。
/* | |
* Copyright Google LLC | |
* | |
* Licensed under the Apache License, Version.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.google.auto.service.processor; | |
import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; | |
import static com.google.auto.common.MoreElements.getAnnotationMirror; | |
import static com.google.auto.common.MoreStreams.toImmutableSet; | |
import static com.google.common.base.Throwables.getStackTraceAsString; | |
import com.google.auto.common.MoreElements; | |
import com.google.auto.common.MoreTypes; | |
import com.google.auto.service.AutoService; | |
import com.google.common.annotations.VisibleForTesting; | |
import com.google.common.collect.HashMultimap; | |
import com.google.common.collect.ImmutableList; | |
import com.google.common.collect.ImmutableSet; | |
import com.google.common.collect.Multimap; | |
import com.google.common.collect.Sets; | |
import java.io.IOException; | |
import java.io.OutputStream; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Set; | |
import java.util.SortedSet; | |
import javax.annotation.processing.AbstractProcessor; | |
import javax.annotation.processing.Filer; | |
import javax.annotation.processing.RoundEnvironment; | |
import javax.annotation.processing.SupportedOptions; | |
import javax.lang.model.SourceVersion; | |
import javax.lang.model.element.AnnotationMirror; | |
import javax.lang.model.element.AnnotationValue; | |
import javax.lang.model.element.Element; | |
import javax.lang.model.element.PackageElement; | |
import javax.lang.model.element.TypeElement; | |
import javax.lang.model.type.DeclaredType; | |
import javax.lang.model.type.TypeMirror; | |
import javax.lang.model.util.SimpleAnnotationValueVisitor; | |
import javax.lang.model.util.Types; | |
import javax.tools.Diagnostic.Kind; | |
import javax.tools.FileObject; | |
import javax.tools.StandardLocation; | |
/** | |
* Processes {@link AutoService} annotations and generates the service provider | |
* configuration files described in {@link java.util.ServiceLoader}. | |
* <p> | |
* Processor Options:<ul> | |
* <li>{@code -Adebug} - turns on debug statements</li> | |
* <li>{@code -Averify=true} - turns on extra verification</li> | |
* </ul> | |
*/ | |
public class AutoServiceProcessor extends AbstractProcessor { | |
static final String MISSING_SERVICES_ERROR = "No service interfaces provided for element!"; | |
private final List<String> exceptionStacks = Collections.synchronizedList(new ArrayList<>()); | |
/** | |
* Maps the class names of service provider interfaces to the | |
* class names of the concrete classes which implement them. | |
* <p> | |
* For example, | |
* {@code "com.google.apphosting.LocalRpcService" -> | |
* "com.google.apphosting.datastore.LocalDatastoreService"} | |
*/ | |
private final Multimap<String, String> providers = HashMultimap.create(); | |
public ImmutableSet<String> getSupportedAnnotationTypes() { | |
return ImmutableSet.of(AutoService.class.getName()); | |
} | |
public SourceVersion getSupportedSourceVersion() { | |
return SourceVersion.latestSupported(); | |
} | |
/** | |
* <ol> | |
* <li> For each class annotated with {@link AutoService}<ul> | |
* <li> Verify the {@link AutoService} interface value is correct | |
* <li> Categorize the class by its service interface | |
* </ul> | |
* | |
* <li> For each {@link AutoService} interface <ul> | |
* <li> Create a file named {@code META-INF/services/<interface>} | |
* <li> For each {@link AutoService} annotated class for this interface <ul> | |
* <li> Create an entry in the file | |
* </ul> | |
* </ul> | |
* </ol> | |
*/ | |
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | |
try { | |
processImpl(annotations, roundEnv); | |
} catch (RuntimeException e) { | |
// We don't allow exceptions of any kind to propagate to the compiler | |
String trace = getStackTraceAsString(e); | |
exceptionStacks.add(trace); | |
fatalError(trace); | |
} | |
return false; | |
} | |
ImmutableList<String> exceptionStacks() { | |
return ImmutableList.copyOf(exceptionStacks); | |
} | |
private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | |
if (roundEnv.processingOver()) { | |
generateConfigFiles(); | |
} else { | |
processAnnotations(annotations, roundEnv); | |
} | |
} | |
private void processAnnotations( | |
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | |
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class); | |
log(annotations.toString()); | |
log(elements.toString()); | |
for (Element e : elements) { | |
// TODO(gak): check for error trees? | |
TypeElement providerImplementer = MoreElements.asType(e); | |
AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get(); | |
Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror); | |
if (providerInterfaces.isEmpty()) { | |
error(MISSING_SERVICES_ERROR, e, annotationMirror); | |
continue; | |
} | |
for (DeclaredType providerInterface : providerInterfaces) { | |
TypeElement providerType = MoreTypes.asTypeElement(providerInterface); | |
log("provider interface: " + providerType.getQualifiedName()); | |
log("provider implementer: " + providerImplementer.getQualifiedName()); | |
if (checkImplementer(providerImplementer, providerType, annotationMirror)) { | |
providers.put(getBinaryName(providerType), getBinaryName(providerImplementer)); | |
} else { | |
String message = | |
"ServiceProviders must implement their service provider interface. " | |
+ providerImplementer.getQualifiedName() | |
+ " does not implement " | |
+ providerType.getQualifiedName(); | |
error(message, e, annotationMirror); | |
} | |
} | |
} | |
} | |
private void generateConfigFiles() { | |
Filer filer = processingEnv.getFiler(); | |
for (String providerInterface : providers.keySet()) { | |
String resourceFile = "META-INF/services/" + providerInterface; | |
log("Working on resource file: " + resourceFile); | |
try { | |
SortedSet<String> allServices = Sets.newTreeSet(); | |
try { | |
// would like to be able to print the full path | |
// before we attempt to get the resource in case the behavior | |
// of filer.getResource does change to match the spec, but there's | |
// no good way to resolve CLASS_OUTPUT without first getting a resource. | |
FileObject existingFile = | |
filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile); | |
log("Looking for existing resource file at " + existingFile.toUri()); | |
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."); | |
} | |
Set<String> newServices = new HashSet<>(providers.get(providerInterface)); | |
if (!allServices.addAll(newServices)) { | |
log("No new service entries being added."); | |
continue; | |
} | |
log("New service file contents: " + allServices); | |
FileObject fileObject = | |
filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile); | |
try (OutputStream out = fileObject.openOutputStream()) { | |
ServicesFiles.writeServiceFile(allServices, out); | |
} | |
log("Wrote to: " + fileObject.toUri()); | |
} catch (IOException e) { | |
fatalError("Unable to create " + resourceFile + ", " + e); | |
return; | |
} | |
} | |
} | |
/** | |
* Verifies {@link ServiceProvider} constraints on the concrete provider class. Note that these | |
* constraints are enforced at runtime via the ServiceLoader, we're just checking them at compile | |
* time to be extra nice to our users. | |
*/ | |
private boolean checkImplementer( | |
TypeElement providerImplementer, | |
TypeElement providerType, | |
AnnotationMirror annotationMirror) { | |
String verify = processingEnv.getOptions().get("verify"); | |
if (verify == null || !Boolean.parseBoolean(verify)) { | |
return true; | |
} | |
// TODO: We're currently only enforcing the subtype relationship | |
// constraint. It would be nice to enforce them all. | |
Types types = processingEnv.getTypeUtils(); | |
if (types.isSubtype(providerImplementer.asType(), providerType.asType())) { | |
return true; | |
} | |
// Maybe the provider has generic type, but the argument to @AutoService can't be generic. | |
// So we allow that with a warning, which can be suppressed with @SuppressWarnings("rawtypes"). | |
// See https://github.com/google/auto/issues/. | |
if (types.isSubtype(providerImplementer.asType(), types.erasure(providerType.asType()))) { | |
if (!rawTypesSuppressed(providerImplementer)) { | |
warning( | |
"Service provider " | |
+ providerType | |
+ " is generic, so it can't be named exactly by @AutoService." | |
+ " If this is OK, add @SuppressWarnings(\"rawtypes\").", | |
providerImplementer, | |
annotationMirror); | |
} | |
return true; | |
} | |
return false; | |
} | |
private static boolean rawTypesSuppressed(Element element) { | |
for (; element != null; element = element.getEnclosingElement()) { | |
SuppressWarnings suppress = element.getAnnotation(SuppressWarnings.class); | |
if (suppress != null && Arrays.asList(suppress.value()).contains("rawtypes")) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Returns the binary name of a reference type. For example, | |
* {@code com.google.Foo$Bar}, instead of {@code com.google.Foo.Bar}. | |
* | |
*/ | |
private String getBinaryName(TypeElement element) { | |
return getBinaryNameImpl(element, element.getSimpleName().toString()); | |
} | |
private String getBinaryNameImpl(TypeElement element, String className) { | |
Element enclosingElement = element.getEnclosingElement(); | |
if (enclosingElement instanceof PackageElement) { | |
PackageElement pkg = MoreElements.asPackage(enclosingElement); | |
if (pkg.isUnnamed()) { | |
return className; | |
} | |
return pkg.getQualifiedName() + "." + className; | |
} | |
TypeElement typeElement = MoreElements.asType(enclosingElement); | |
return getBinaryNameImpl(typeElement, typeElement.getSimpleName() + "$" + className); | |
} | |
/** | |
* Returns the contents of a {@code Class[]}-typed "value" field in a given {@code | |
* annotationMirror}. | |
*/ | |
private ImmutableSet<DeclaredType> getValueFieldOfClasses(AnnotationMirror annotationMirror) { | |
return getAnnotationValue(annotationMirror, "value") | |
.accept( | |
new SimpleAnnotationValueVisitor<ImmutableSet<DeclaredType>, Void>(ImmutableSet.of()) { | |
public ImmutableSet<DeclaredType> visitType(TypeMirror typeMirror, Void v) { | |
// TODO(ronshapiro): class literals may not always be declared types, i.e. | |
// int.class, int[].class | |
return ImmutableSet.of(MoreTypes.asDeclared(typeMirror)); | |
} | |
public ImmutableSet<DeclaredType> visitArray( | |
List<? extends AnnotationValue> values, Void v) { | |
return values.stream() | |
.flatMap(value -> value.accept(this, null).stream()) | |
.collect(toImmutableSet()); | |
} | |
}, | |
null); | |
} | |
private void log(String msg) { | |
if (processingEnv.getOptions().containsKey("debug")) { | |
processingEnv.getMessager().printMessage(Kind.NOTE, msg); | |
} | |
} | |
private void warning(String msg, Element element, AnnotationMirror annotation) { | |
processingEnv.getMessager().printMessage(Kind.WARNING, msg, element, annotation); | |
} | |
private void error(String msg, Element element, AnnotationMirror annotation) { | |
processingEnv.getMessager().printMessage(Kind.ERROR, msg, element, annotation); | |
} | |
private void fatalError(String msg) { | |
processingEnv.getMessager().printMessage(Kind.ERROR, "FATAL ERROR: " + msg); | |
} | |
} |
AutoService源码分析
主要逻辑在process方法中,通过实现AbstractProcessor的process方法来实现功能。
process委托给了processImpl:
private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { | |
if (roundEnv.processingOver()) { //本轮注解处理完毕 | |
generateConfigFiles();//生成SPI注册文件 | |
} else { //未处理完毕,继续处理 | |
processAnnotations(annotations, roundEnv);//整理需要注册的文件,放入缓存 | |
} | |
} |
再看processAnnotations方法,笔者已经加了注释:
private void processAnnotations( | |
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){ | |
//获取所有加了AutoService注解的类 | |
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class); | |
for (Element e : elements) { | |
//将Element转成TypeElement | |
TypeElement providerImplementer = MoreElements.asType(e); | |
//获取AutoServce注解指定的value | |
AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get(); | |
//获取value集合 | |
Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror); | |
//如果没有指定value,报错 | |
if (providerInterfaces.isEmpty()) { | |
error(MISSING_SERVICES_ERROR, e, annotationMirror); | |
continue; | |
} | |
//遍历所有的value,获取value的完整类名(例如javax.annotation.processing.Processor) | |
for (DeclaredType providerInterface : providerInterfaces) { | |
TypeElement providerType = MoreTypes.asTypeElement(providerInterface); | |
//判断是否是继承关系,是则放入providers缓存起来,否则报错 | |
if (checkImplementer(providerImplementer, providerType, annotationMirror)) { | |
providers.put(getBinaryName(providerType), getBinaryName(providerImplementer)); | |
} else { | |
//报错代码,略 | |
} | |
} | |
} | |
} |
注解处理完毕,就会生成SPI注册文件。如果SPI路径上文件已经存在,先要把已存在的SPI清单读进内存,再把新的provider加进去,然后全部写出,覆盖原来的文件。这部分逻辑如下:
private void generateConfigFiles() { | |
Filer filer = processingEnv.getFiler();//获取文件工具类,processingEnv是AbstractProcessor的成员变量,直接拿来用。 | |
//遍历之前解析的providers缓存 | |
for (String providerInterface : providers.keySet()) { | |
//providerInterface就是value字段指定的接口,例如javax.annotation.processing.Processor | |
String resourceFile = "META-INF/services/" + providerInterface; | |
log("Working on resource file: " + resourceFile); | |
try { | |
SortedSet<String> allServices = Sets.newTreeSet(); | |
try { | |
//已经存在的SPI文件 | |
FileObject existingFile = | |
filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile); | |
//SPI文件中的service条目清单 | |
Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream()); | |
log("Existing service entries: " + oldServices); | |
allServices.addAll(oldServices); | |
} catch (IOException e) { | |
log("Resource file did not already exist."); | |
} | |
//新的service条目清单 | |
Set<String> newServices = new HashSet<>(providers.get(providerInterface)); | |
//如果已经存在,则不处理 | |
if (!allServices.addAll(newServices)) { | |
log("No new service entries being added."); | |
continue; | |
} | |
//以下是将缓存的services写入文件中。 | |
log("New service file contents: " + allServices); | |
FileObject fileObject = | |
filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile); | |
try (OutputStream out = fileObject.openOutputStream()) { | |
ServicesFiles.writeServiceFile(allServices, out); | |
} | |
log("Wrote to: " + fileObject.toUri()); | |
} catch (IOException e) { | |
fatalError("Unable to create " + resourceFile + ", " + e); | |
return; | |
} | |
} | |
} |
可见AutoServiceProcessor的主要功能就是将加了AutoService注解的类,加到SPI注册文件中。SPI文件名称(或者叫服务)可以通过value指定。
下面将AutoService从mavenCentral仓库中下载下来(一个jar包),解压查看它的内容:
可以看到它里面内容并不多,主要就是一个AutoServiceProcessor类和一个APT清单文件。打开这个清单文件,里面就是AutoServiceProcessor类的全路径:
所以我们将AutoService加到java项目中,其实就是引入了AutoServiceProcessor这个注解处理器,帮助我们处理@AutoService注解,将我们的服务(一般是APT类,也可以是其它的类,通过value指定)自动注册进SPI文件中。
看到这里,不知道读者有没有领悟。
AutoService是一个注解处理器,我们自己开发的APT也是注解处理器,它们都是注解处理器,AutoSevice是自动帮我们注册注解处理器的注解处理器。是不是有点绕?
当然AutoService的作用不仅在于注册APT,还可以注册其它服务。只是注册APT我们比较常见。
再举一个AutoService的使用场景:
在组件化架构app中,有一个主Module和若干业务Module,如何在主Module中初始化各个业务Module?这可以使用SPI技术,在业务Module中创建一个初始化类实现一个共同的接口,然后在这个类上加AutoService注解,在主Module中就可以通过SPI机制加载这些业务Module的初始化类,调用初始化接口。
AutoService不仅是一个自动注册APT的框架,它还是一个SPI技术的模板,有时候我们需要自己开发一个基于APT同时又要注册自定义service的框架,它的源码是一个很好的参考。AutoServiceProcessor里面的大部分代码是可以复制拿来用。再比如,ServiceFiles.java是SPI资源文件读取和写入的工具类,直接复制到我们项目中即可。