java注解处理器之Google Auto Service
介绍
SPI 是 java 提供的一种服务发现的标准,具体请看SPI 介绍,但每次我们都需要自己创建 services 目录,以及配置文件,google 的 autoservice 就可以帮我们省去这一步。
使用
maven 的依赖
复制<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<version>1.0-rc6</version>
<optional>true</optional>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.0-rc6</version>
<optional>true</optional>
<scope>compile</scope>
</dependency>
定义接口
public interface UserService {
String userName();
}
定义接口实现,使用 AutoService 注解
@AutoService(UserService.class)
public class LocalUserService implements UserService {
@Override
public String userName() {
return "local user";
}
}
复制@AutoService(UserService.class)
public class RemoteUserService implements UserService {
@Override
public String userName() {
return "remote user";
}
}
调用
复制public class Client {
public static void main(String[] args) {
ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);
for (UserService userService : serviceLoader) {
System.out.println(userService.userName());
}
}
}
输出结果为
复制local user
remote user
从编译之后的目录里可以看到已经生成了对应的配置文件
实现
@SupportedOptions({"debug", "verify"})
public class AutoServiceProcessor extends AbstractProcessor {
@Override
// 处理
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){
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;
}
}
// 真正处理
private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 注解已经处理完毕了,创建配置文件
if (roundEnv.processingOver()) {
generateConfigFiles();
} else {
// 处理注解
processAnnotations(annotations, roundEnv);
}
return true;
}
// 处理注解
private void processAnnotations(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
Set<? <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Element</span>> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
<span class="hljs-keyword">for</span> (Element e : elements) {
<span class="hljs-type">TypeElement</span> <span class="hljs-variable">providerImplementer</span> <span class="hljs-operator">=</span> (TypeElement) e;
<span class="hljs-type">AnnotationMirror</span> <span class="hljs-variable">annotationMirror</span> <span class="hljs-operator">=</span> getAnnotationMirror(e, AutoService.class).get();
Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
<span class="hljs-keyword">if</span> (providerInterfaces.isEmpty()) {
<span class="hljs-keyword">continue</span>;
}
<span class="hljs-keyword">for</span> (DeclaredType providerInterface : providerInterfaces) {
<span class="hljs-type">TypeElement</span> <span class="hljs-variable">providerType</span> <span class="hljs-operator">=</span> MoreTypes.asTypeElement(providerInterface);
<span class="hljs-keyword">if</span> (checkImplementer(providerImplementer, providerType)) {
providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));
} <span class="hljs-keyword">else</span> {
}
}
}
}
// 创建配置文件
private void generateConfigFiles() {
Filer filer = processingEnv.getFiler();
for (String providerInterface : providers.keySet()) {
String resourceFile = "META-INF/services/" + providerInterface;
try {
SortedSet<String> allServices = Sets.newTreeSet();
try {
FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
allServices.addAll(oldServices);
} catch (IOException e) {
}
Set<String> newServices = new HashSet<String>(providers.get(providerInterface));
if (allServices.containsAll(newServices)) {
return;
}
allServices.addAll(newServices);
FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
resourceFile);
OutputStream out = fileObject.openOutputStream();
ServicesFiles.writeServiceFile(allServices, out);
out.close();
} catch (IOException e) {
fatalError("Unable to create" + resourceFile + "," + e);
return;
}
}
}
}
AutoServoce 工具和 Lombok 工具是类似的实现原理,通过 java 提供的注解处理器机制,在编译期帮助我们创建一些文件或修改文件。