【Android】Google AutoService usage and source code analysis

AutoService is a framework developed by Google to automatically generate SPI manifest files. Readers who have seen the source code of some APT-based tripartite frameworks should understand. Such as Router, EventBus and so on. Generally, we use it to automatically register APT files for us (the full name is Annotation Process Tool, or annotation processor, the implementation of AbstractProcessor). Many frameworks that generate SPI files also plagiarize its source code, which shows that its role is not small.

APT is actually a tool based on SPI, which is an interface left by JDK for developers to process annotations before compilation. APT is also an application of SPI. About SPI and APT will be discussed in detail below.

Let's talk about how it is used first.

Use of AutoService

The function of the AutoService framework is to automatically generate the SPI manifest file (the file under META-INF/services). It’s okay without it. If you don’t use it, you need to manually create this file and manually add services (interface implementation) to this file.

A more common scenario for AutoService is to help register APT (annotation processor). The following is an example of APT to explain its use.

The development of APT needs to be developed in the Java SE project, because inheritance is required AbstractProcessor, and AbstractProcessorit works in the Java compilation stage.

First create a Java module, which can also be created in Android Studio, and then build.gradleadd dependencies in it, as shown in the dependencies section below.

By annotationProcessoradding the annotation processor (AutoServiceProcessor.class), you need to implementationadd annotation dependencies, namely 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'
}

Then just add the @AutoService annotation above your annotation processor class, and the value is specified as a javax.annotation.processing.Processorclass, because the name of the SPI manifest file (file under META-INF/services) to be generated is that
javax.annotation.processing.Processorthis Processor is built in Java, before Javac is compiled The default annotation processor interface. If it is our custom interface, specify it as your own interface name.

@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 is inherited from the Processor interface:

public abstract class AbstractProcessor implements Processor {
    
    
	...
}

The AbstractProcessor class is in JDK SE, and the Android Framework deleted it (because it is not needed or used), so it does not exist in the Android Module. This also explains why the Java SE project was created to write APT code.

The declaration of the AutoService annotation is as follows. Its value is a class collection, and multiple values ​​can be specified.

@Documented
@Retention(CLASS)
@Target(TYPE)
public @interface AutoService {
    
    
  /** Returns the interfaces implemented by this service provider. */
  Class<?>[] value();
}

The role in the above example MyProcessoris to process custom annotations of the project. For example, the Arouter framework will use it to process @Aouter annotations and automatically generate route registration classes.

After compiling this Java project, it will be automatically MyProcessoradded to the SPI registration file of APT.

It should be noted that MyProcessor does not work at this time, and neither the init nor process methods will be executed. Because it is not in the SPI registration file during the annotation processing phase, it is registered after the annotation processing phase is completed. Package the Java project into a jar, and this MyProcessor will be in the SPI registration file. Other projects depend on this jar, and the code of MyProcessor will be executed.

The above is the use of AutoService. Having said this, some people may not understand. It doesn't matter, first understand the SPI technology.

About SPI

What is SPI? Understanding SPI is the basis for understanding AutoService.

SPI is Service Provider Interfacethe abbreviation of SPI, which is a mechanism provided by JDK by default to separate interfaces and implementation classes. This mechanism can decouple the interface and implementation, greatly improving the scalability of the system.

SPI mechanism agreement: When a Jar package needs to provide an implementation class of an interface, the Jar package needs to META-INF/servicescreate a file named after the service interface in the directory at the same time. This file is the concrete implementation class that implements the service interface. When the external program assembles this module, the specific implementation class name can be found through the configuration file in the Jar package META-INF/services/, and loaded and instantiated to complete the injection of the module.

SPI example

For example, there is an interface IMyService

package com.devnn.demo.interface
  
public interface IMyService {
    
    

    void hello();
}

Its implementation classes are:

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/servicesCreate a file in the directory , com.devnn.demo.interface.IMyServicethe content is the full name of all implementation classes:

com.devnn.demo.impl.MyServiceImpl_1
com.devnn.demo.impl.MyServiceImpl_2

Project structure:
insert image description here

Load all subclasses of the IMyService interface:

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();
        }
    }
}

Running it will print:

Hi,I am MyServiceImpl_1
Hi,I am MyServiceImpl_2

Isn't it amazing, through an interface, you can find its implementation class, this is the role of SPI.

APT technology

Then let's talk about APT. At the beginning, APT is an application of SPI. Why do you say that? APT is actually the built-in SPI interface provided by Java, which is used to process annotations in java source code before compiling java.
The service interface of APT is this, which
javax.annotation.processing.Processor
is consistent with the file name under META_INF/service.

The Java compiler reads this manifest file, loads all classes that implement this interface, and completes the user's annotation processing logic.

AutoService source code

Then go back to AutoService and analyze it in combination with the source code. The main code of AutoService is just one class, namely AutoServiceProcessor.java. For the convenience of reading, I copy it here first, and then analyze it later.

/*
 * 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 source code analysis

The main logic is in the process method, and the function is realized by implementing the process method of AbstractProcessor.

Process is delegated to processImpl:

 private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
    
   if (roundEnv.processingOver()) {
    
     //本轮注解处理完毕
      generateConfigFiles();//生成SPI注册文件
    } else {
    
     //未处理完毕,继续处理
      processAnnotations(annotations, roundEnv);//整理需要注册的文件,放入缓存
    }
 }

Looking at the processAnnotations method again, the author has added a comment:

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 {
    
    
         //报错代码,略
        }
      }
    }
  }

After the annotation is processed, the SPI registration file will be generated. If the file on the SPI path already exists, first read the existing SPI list into the memory, then add the new provider, and then write out all of them to overwrite the original file. This part of the logic is as follows:

  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;
      }
    }
  }

The visible AutoServiceProcessormain function is to add the class annotated with AutoService to the SPI registration file. The SPI file name (or service) can be specified by value.

Next, download AutoService from the mavenCentral repository (a jar package), unzip it and view its contents:
insert image description here

You can see that there are not many contents in it, mainly a AutoServiceProcessorclass and an APT manifest file. Open this manifest file, which contains AutoServiceProcessorthe full path of the class:

insert image description here

So when we add AutoService to the java project, we actually introduce the AutoServiceProcessor annotation processor to help us process the @AutoService annotation and automatically register our services (usually APT classes, or other classes, specified by value) into the SPI file.

Seeing this, I don’t know if the readers have comprehended it.

AutoService is an annotation processor. The APT we developed is also an annotation processor. They are all annotation processors. AutoSevice is an annotation processor that automatically registers annotation processors for us. Is it a bit convoluted?

Of course, the role of AutoService is not only to register APT, but also to register other services. It's more common for us to register APT.

Another AutoService usage scenario:

In a componentized architecture app, there is a main module and several business modules, how to initialize each business module in the main module? This can use SPI technology to create an initialization class in the business module to implement a common interface, and then add AutoService annotations to this class. In the main module, the initialization classes of these business modules can be loaded through the SPI mechanism, and the initialization interface is called.

AutoService is not only a framework for automatic registration of APT, but also a template of SPI technology. Sometimes we need to develop a framework based on APT and register custom service at the same time. Its source code is a good reference. Most of the code in AutoServiceProcessor can be copied and used. For another example, ServiceFiles.java is a tool class for reading and writing SPI resource files, which can be directly copied to our project.

Guess you like

Origin blog.csdn.net/devnn/article/details/126837081