網站首頁 編程語言 正文
一般我們用它來自動幫我們注冊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編譯前默認的注解處理器接口。如果是我們自定義的接口就指定成自己的接口名。
@AutoService(value = {Processor.class}) public class MyProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment processingEnv) { System.out.println("MyProcessor------------init---------------"); super.init(processingEnv); } @Override 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_1 implements IMyService { @Override public void hello() { System.out.println("Hi,I am MyServiceImpl_1"); } }
package com.devnn.demo.impl; import com.devnn.demo.interfaces.devnnService; public class MyServiceImpl_2 implements IMyService { @Override public void hello() { System.out.println("Hi,I am MyServiceImpl_2"); } }
在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 2008 Google LLC * * Licensed under the Apache License, Version 2.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-2.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.SimpleAnnotationValueVisitor8; 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> */ @SupportedOptions({"debug", "verify"}) public class AutoServiceProcessor extends AbstractProcessor { @VisibleForTesting 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(); @Override public ImmutableSet<String> getSupportedAnnotationTypes() { return ImmutableSet.of(AutoService.class.getName()); } @Override 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> */ @Override 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/870. 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 SimpleAnnotationValueVisitor8<ImmutableSet<DeclaredType>, Void>(ImmutableSet.of()) { @Override 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)); } @Override 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資源文件讀取和寫入的工具類,直接復制到我們項目中即可。
原文鏈接:https://blog.csdn.net/devnn/article/details/126837081
相關推薦
- 2022-07-17 C++深入講解namespace與string關鍵字的使用_C 語言
- 2023-05-29 tf.nn.conv2d與tf.layers.conv2d的區別及說明_python
- 2022-07-13 CentOS上Autofs自動掛載iso光盤鏡像-Linux
- 2022-06-07 Python必備技巧之函數的使用詳解_python
- 2022-09-16 Go1.16新特性embed打包靜態資源文件實現_Golang
- 2022-09-18 Golang?模塊引入及表格讀寫業務快速實現示例_Golang
- 2023-03-15 Android?Studio?全局查找問題_Android
- 2023-10-09 instanceof` 的基本工作原理
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支