zoukankan      html  css  js  c++  java
  • 【java编程】ServiceLoader使用看这一篇就够了

    转载:https://www.jianshu.com/p/7601ba434ff4

    想必大家多多少少听过spi,具体的解释我就不多说了。但是它具体是怎么实现的呢?它的原理是什么呢?下面我就围绕这两个问题来解释:

    实现: 其实具体的实现类就是java.util.ServiceLoader这个类。

    要想了解一个机制的原理,首先得知道它是怎么运行的,需要什么配置,才能运行起来。然后再分解来了解实现。对于技术实现也是一样,先看这个类是怎么实现的,先让它跑起来,看到效果。然后再讲原理。
    按照使用说明文档,应该分下面几个步骤来使用:

    1. 创建一个接口文件
    2. 在resources资源目录下创建META-INF/services文件夹
    3. 在services文件夹中创建文件,以接口全名命名
    4. 创建接口实现类

    我们想测试一下,一般是在这个工程中建立一个测试类来测试。来看下代码片段:

    接口类

    public interface IMyServiceLoader {
    
        String sayHello();
    
        String getName();
    }
    View Code

    实现类:

    public class MyServiceLoaderImpl1 implements IMyServiceLoader {
        @Override
        public String sayHello() {
            return "hello1";
        }
    
        @Override
        public String getName() {
            return "name1";
        }
    }
    
    public class MyServiceLoaderImpl2 implements IMyServiceLoader {
        @Override
        public String sayHello() {
            return "hello2";
        }
    
        @Override
        public String getName() {
            return "name2";
        }
    }
    View Code

    测试类:

    public class TestMyServiceLoader {
        public static void main(String[] argus){
            ServiceLoader<IMyServiceLoader> serviceLoader = ServiceLoader.load(IMyServiceLoader.class);
            for (IMyServiceLoader myServiceLoader : serviceLoader){
                System.out.println(myServiceLoader.getName() + myServiceLoader.sayHello());
            }
        }
    }
    View Code

    正常情况下这里应该输出

    name2hello2
    name1hello1
    View Code

    看了这些步骤,想必你也知道原理了,我在这里总结下。

     原理:在ServiceLoader.load的时候,根据传入的接口类,遍历META-INF/services目录下的以该类命名的文件中的所有类,并实例化返回。

    相信看到这里,有的看客该爆粗话了,说啥子看着一篇就够了,这些知识点随便一搜,到处都是好伐。是的,上面说的,确实随便一搜都可以搜到,所以这里我要划重点了:

    一、问题

    上面说了,正常情况下会那样输出,但是你运行程序你就会发现,马丹,怎么不起作用啊,我哪里做错了,都是按照文章步骤来做的。弄的你都开始怀疑人生了。不要怀疑人生,在一个工程中做测试,确实不能实现想要的效果。

    二、回忆场景

    回忆一下spi的使用场景。它是给制作标准的一放用的,用来指定标准,然后不同实现方,用不同的方式实现标准供使用方使用。那标准方和实现方必然不是一个。想到这里,你应该能够向明白了吧。

    三、解决方案

    要解决问题,就把之前做的打jar包,引入新工程测试,这样就可以了。

    四、疑问

    但是有人会说标准方和实现方也可能是一个啊,好比标准方我提供一个内部的实现方案也是可以的啊。也确实有道理啊,那这种怎么实现呢?

    五、思考

    当然也有办法,下面就说下实现方法。想要实现上面的需求,首先要知道拦阻这个需求实现的问题,然后把这些问题都解决了,需求自然也就实现了。那就先来分析问题吧,为什么在一个工程中获取不到接口的实现类呢?经过观察发现是因为资源文件没有在classPath中,为什么这么说呢,可以看下build的目录下面是没有META-INF文件夹。现在知道了原因,这么解决呢?

    六、疑问临时解决方案

    最简单的方法,把资源下的META-INF文件夹拷贝到build目录下,然后再运行,发现可以了,这也就验证了,确实是这个问题造成的。搞定!

    七、再次发出疑问

    这样就结束了,那我总不能手动拷贝吧,这不算解决方案,只是临时方案。那要怎么解决呢?

      我就不卖关子了。其实要解决这个问题,只要在编译的时候把这些文件放到build目录中就行了,是不是很简单。思路是有了,可是怎么实现呢?这个时候要用到拦截编译处理,然后再里面做这件事情。

    方案一
    继承AbsStractProcessor,在process方法中把资源文件移到build目录下。

    方案二
    这里用到了google开源的AutoService

    大概看了下autoService的源码,其实它也是使用方案一的方法,拦截编译过程,然后再build目录下生成配置文件,这里来大概看下它的process方法:

    ublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        try {
          return processImpl(annotations, roundEnv);
        } catch (Exception e) {
          ...
          return true;
        }
      }
    
    private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (roundEnv.processingOver()) {
          generateConfigFiles();
        } else {
          processAnnotations(annotations, roundEnv);
        }
        return true;
      }
    View Code

    这里你会发现其实就是generateConfigFiles()processAnnotations(annotations, roundEnv)看名字可以猜到processAnnotations是处理注解的,这里实现类都实现了注解,所以这里应该是找到实现类。

    rivate void processAnnotations(Set<? extends TypeElement> annotations,
          RoundEnvironment roundEnv) {
    
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
        for (Element e : elements) {
          TypeElement providerImplementer = (TypeElement) e;
          AnnotationMirror providerAnnotation = getAnnotationMirror(e, AutoService.class).get();
          DeclaredType providerInterface = getProviderInterface(providerAnnotation);
          TypeElement providerType = (TypeElement) providerInterface.asElement();
          ...
          String providerTypeName = getBinaryName(providerType);
          String providerImplementerName = getBinaryName(providerImplementer);
          providers.put(providerTypeName, providerImplementerName);
        }
      }
    View Code

    确实如此,这里会把所有的实现类存起来。

    再来看看generateConfigFiles()方法

    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));
    
            allServices.addAll(newServices);
            FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
                resourceFile);
            OutputStream out = fileObject.openOutputStream();
            ServicesFiles.writeServiceFile(allServices, out);
            out.close();
          } catch (IOException e) {
            return;
          }
        }
      }
    View Code

    这里是在build下创建META-INF目录。和我们想的一模一样。

    八、总结

    好了,要实现文章开头的需求,除非你觉得你比google开源AutoService的工程师写的更好,不然就直接使用AutoService吧。这篇文章不仅是分析ServiceLoader的原理,实现我们的需求,更重要的是高速我们遇到问题该怎么分析问题,解决问题。

    九、扩展

    其实还有很多比较好玩的,比如在拦截到编译过程时,可以再编译期生成一些有意思的代码,来帮我们实现一些自动化处理。这就需要动用我们的大脑就想了,介绍一下生成代码的库javapoet,大家可以了解一下。

  • 相关阅读:
    BZOJ 1101 莫比乌斯函数+分块
    BZOJ 2045 容斥原理
    BZOJ 4636 (动态开节点)线段树
    BZOJ 2005 容斥原理
    BZOJ 2190 欧拉函数
    BZOJ 2818 欧拉函数
    BZOJ 3123 主席树 启发式合并
    812. Largest Triangle Area
    805. Split Array With Same Average
    794. Valid Tic-Tac-Toe State
  • 原文地址:https://www.cnblogs.com/shangxiaofei/p/10548921.html
Copyright © 2011-2022 走看看