SPI 机制(二) — AutoService 解析

一、概述

SPI 全称为Service Provider Interface,是JDK内置的一种服务提供发现机制。简单来说,它是一种动态替换发现机制。例如,在设计组件化路由框架的时候就会使用到 SPI 设计思想。

在上一篇文章中,我们已经分析了 ServiceLoader 的加载流程,知道它会从指定的路径下解析文件来获取到指定的子类信息,那么这些子类信息又是如何生成的呢?本篇文章我们就来分析一下 AutoService 库是如何实现的。

AutoService 的依赖:

annotationProcessor 'com.google.auto.service:auto-service-annotations:1.0-rc7'
implementation 'com.google.auto.service:auto-service:1.0-rc7'

关联文章:

  1. SPI 机制(一) — ServiceLoader 解析
  2. SPI 机制(二) — AutoService 解析
  3. AutoService — github仓库地址

二、原理

原理其实很简单,主要是利用:编译期注解、注解处理器。

过程主要分为三步:

  1. 在开发阶段,我们会对要关联的类使用编译期注解。
  2. 在编译阶段,我们通过注解处理器去遍历查找被指定注解修饰的类的信息,然后收集到后写入到 META-INF/services 文件夹下的文件中。
  3. 在使用阶段,通过 ServiceLoaderMETA-INF/services 文件夹下查找指定的文件,并解析文件内容,获取到要加载的子类信息。

三、AutoServiceProcessor 源码解析

AutoServiceProcessor.process()

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  return processImpl(annotations, roundEnv);
}

private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  // 注解处理操作是否结束
  if (roundEnv.processingOver()) {
    generateConfigFiles(); //2.处理过程结束后生成文件。
  } else {
  	// 1.会先执行注解
    processAnnotations(annotations, roundEnv);
  }
  return true;
}

小结:
AutoServiceProcessor.process() 操作会有两个过程:

  1. 查找注解修饰的类,并收集信息: processAnnotations()
  2. 将收集的信息写入指定路径的文件中: generateConfigFiles()

下面分析一下查找注解修饰类的流程

AutoServiceProcessor.processAnnotations()

// Multimap 的特点,在Multimap内部,一个key其实是对应一个Collection集合的。
private Multimap<String, String> providers = HashMultimap.create();

private void processAnnotations(Set<? extends TypeElement> annotations,
  		RoundEnvironment roundEnv) {
  // 1.获取被注解 AutoService 修饰的类的集合。
  Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);

  log(annotations.toString());
  log(elements.toString());
  
  // 2.遍历被注解 AutoService 修饰的类
  for (Element e : elements) {
    // TODO(gak): check for error trees?
    TypeElement providerImplementer = (TypeElement) e;
    // 3.获取被修饰的类中的注解。
    AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();
   // 4.第3步中获取到的注解中获取 value 值的信息。
    Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
    if (providerInterfaces.isEmpty()) {
      error(MISSING_SERVICES_ERROR, e, annotationMirror);
      continue;
    }
    // 5.遍历在注解value字段中赋值的接口信息。
    for (DeclaredType providerInterface : providerInterfaces) {
      TypeElement providerType = MoreTypes.asTypeElement(providerInterface);
	  // 6.判断该子类跟注解中value里存的接口类型是否一致。
      if (checkImplementer(providerImplementer, providerType)) {
        // 7.该子类是 AutoService.value 中接口的子类,就存放到 providers 集合中。
        // 将数据写入文件时,会从 providers 取数据。
        providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));
      } else {
        // ...省略异常处理...
      }
    }
  }
}

/**
 * 这个方法会对有些特殊的文件名做一些处理。
 * 如:com.google.Foo$Bar 会替换成 com.google.Foo.Bar
 */
private String getBinaryName(TypeElement element) {
  return getBinaryNameImpl(element, element.getSimpleName().toString());
}

小结: 执行注解处理的流程:

  1. 获取被注解 AutoService 修饰的类的集合。
  2. 遍历被注解 AutoService 修饰的类。
  3. 获取被修饰的类中的注解。
  4. 从第3步中获取到的注解中获取 value 值的信息。
  5. 遍历在注解value字段中赋值的接口信息。
  6. 判断该子类跟注解中value里存的接口类型是否一致。
  7. 该子类是 AutoService.value 中接口的子类,就存放到 providers 集合中。当数据要写入文件时,会从 providers 取数据。

接下来分析一下文件生成的流程。

AutoServiceProcessor.generateConfigFiles()

private void generateConfigFiles() {
  Filer filer = processingEnv.getFiler();
  
  // 1.从providers中提取接口(keys)集合,用于创建文件。
  for (String providerInterface : providers.keySet()) {
    // 2.根据keys里面的接口信息,拼接处文件名。一个接口对应一个文件。
    String resourceFile = "META-INF/services/" + providerInterface;
    try {
      SortedSet<String> allServices = Sets.newTreeSet();
      try {
        FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
        // 3.如果之前文件已存在,就读取出文件的数据。
        Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
        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.取出providers中指定接口对应的子类集合。
      Set<String> newServices = new HashSet<String>(providers.get(providerInterface));
      if (allServices.containsAll(newServices)) {
        log("No new service entries being added.");
        return;
      }
	  // 5.将老的数据和从providers中取出的数据进行合并。
      allServices.addAll(newServices);
      log("New service file contents: " + allServices);
      FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
      OutputStream out = fileObject.openOutputStream();
      // 6.将数据重新写入文件中。
      ServicesFiles.writeServiceFile(allServices, out);
      out.close();
      log("Wrote to: " + fileObject.toUri());
    } catch (IOException e) {
      fatalError("Unable to create " + resourceFile + ", " + e);
      return;
    }
  }
}

小结: 将信息写入文件的流程:

  1. 从providers中提取接口(keys)集合,用于创建文件。
  2. 根据keys里面的接口信息,拼接处文件名,一个接口对应一个文件。
  3. 如果之前文件已存在,就读取出之前文件的数据,并保存到内存中,便于和后面的数据进行合并。
  4. 取出providers中指定接口对应的子类集合。
  5. 将从文件中读取的数据和从providers中读取的数据进行合并。
  6. 将数据重新写入文件中。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的autoservice注解是一种用于服务发现的机制,它能够自动将服务的实现类注册到指定的配置文件中,一般是META-INF/services目录下的文件。 然而,的确有一些情况下autoservice注解无法生成META-INF文件。这可能是由于以下几种原因导致的: 1. 编译问题:可能是由于编译配置的问题,导致编译器无法正确处理autoservice注解。这种情况下,我们可以尝试检查编译器的配置,或者使用其他IDE或编译工具进行尝试。 2. 库或框架限制:有些库或框架可能不支持autoservice注解,或者在使用autoservice注解时会有特殊的要求。我们可以查看相关库或框架的文档,了解其对autoservice注解的支持情况,或者尝试其他方式实现服务发现。 3. 配置文件缺失或错误:autoservice注解生成META-INF文件的前提是META-INF目录存在,并且配置文件的名称和路径正确。如果自动生成的META-INF文件不存在,或者文件名或路径有误,就无法实现服务发现。我们需要检查项目的文件结构,确认META-INF目录是否存在,并且配置文件的名称和路径是否正确。 总之,虽然autoservice注解通常可以自动将服务实现类注册到META-INF文件中,但在某些情况下可能会遇到无法生成META-INF文件的问题。我们需要仔细检查编译配置、库或框架限制,以及配置文件是否正确等因素,以找出问题所在,并采取相应的解决方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值