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

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

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

要想了解一个机制的原理,首先得知道它是怎么运行的,需要什么配置,才能运行起来。然后再分解来了解实现。对于技术实现也是一样,先看这个类是怎么实现的,先让它跑起来,看到效果。然后再讲原理。

按照使用说明文档,应该分下面几个步骤来使用:

创建一个接口文件

在resources资源目录下创建META-INF/services文件夹

在services文件夹中创建文件,以接口全名命名

创建接口实现类

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

接口类

public interfaceIMyServiceLoader {

String sayHello();

String getName();

}

View Code

实现类:

public class MyServiceLoaderImpl1 implementsIMyServiceLoader {

@OverridepublicString sayHello() {return "hello1";

}

@OverridepublicString getName() {return "name1";

}

}public class MyServiceLoaderImpl2 implementsIMyServiceLoader {

@OverridepublicString sayHello() {return "hello2";

}

@OverridepublicString getName() {return "name2";

}

}

View Code

测试类:

public classTestMyServiceLoader {public static voidmain(String[] argus){

ServiceLoader 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{returnprocessImpl(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 voidgenerateConfigFiles() {

Filer filer=processingEnv.getFiler();for(String providerInterface : providers.keySet()) {

String resourceFile= "META-INF/services/" +providerInterface;try{

SortedSet allServices =Sets.newTreeSet();try{

FileObject existingFile= filer.getResource(StandardLocation.CLASS_OUTPUT, "",

resourceFile);

Set oldServices =ServicesFiles.readServiceFile(existingFile.openInputStream());

allServices.addAll(oldServices);

}catch(IOException e) {

}

Set newServices = new HashSet(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,大家可以了解一下。

serviceloader java_【java编程】ServiceLoader使用看这一篇就够了相关推荐

  1. java面试题,看我这篇就够了,前端后台应有尽有,包你通过面试

    面试题精华版:https://blog.csdn.net/cencong863251/article/details/88963573 以下为详情版: HTML&CSS部分 1.HTML中定义 ...

  2. 学java日志框架,看这一篇就够了!!!

    什么是日志框架 日志框架的选择 Logback的使用与配置 什么是日志框架 是一套能实现日志输出的工具包 能够描述系统运行状态的所有时间都可以算作日志 日志框架的能力 定制输出目标 定制输出格式 携带 ...

  3. java并发编程入门_Java并发编程入门,看这一篇就够了

    2.3 资源限制的挑战 什么是资源限制 资源限制指在进行并发编程时,程序的执行速度受限于计算机硬件资源或软件资源. 硬件资源包括:带宽的上传下载速度.硬盘读写速度和CPU的处理速度等 软件资源包括:线 ...

  4. java11模块化开发_【JDK 11】关于 Java 模块系统,看这一篇就够了

    继 2014 年 3 月 Java 8 发布之后,时隔 4 年,2018 年 9 月,Java 11 如期发布,其间间隔了 Java 9 和 Java 10 两个非LTS(Long Term Supp ...

  5. 关于 Java 模块系统,看这一篇就够了

    作者 | Emac 杏仁医生架构师兼平台组负责人,关注为服务.DevOps领域. 继 2014 年 3 月 Java 8 发布之后,时隔 4 年,2018 年 9 月,Java 11 如期发布,其间间 ...

  6. java分布式项目,看这一篇就够了!

    三.堆空间 基本描述 JVM启动时创建堆区,是内存管理的核心区,通常情况下也是最大的内存空间,是被所有线程共享的,几乎所有的对象实例都要在堆中分配内存,所以这里也是垃圾回收的重点空间. 堆栈关系 栈是 ...

  7. Java socket详解,看这一篇就够了

    刚给大家讲解Java socket通信后,好多童鞋私信我,有好多地方不理解,看不明白.特抽时间整理一下,详细讲述Java socket通信原理和实现案例.整个过程楼主都是通过先简单明了的示例让大家了解 ...

  8. Java学习路线图,看这一篇就够了!

    主要分为三阶段 | 耗废1024根秀发,Java学习路线图来了,整合了自己所学的所有技术整理出来的2022最新版Java学习路线图,适合于初.中级别的Java程序员.可以按照这个序号来学习的,或者把知 ...

  9. Java Comparator使用指南 ---- 看这一篇就够了

    目录 Comparator: Comparator的Default方法: Comparable接口 在Java学习过程中,Arrays.sort()可以说是我写过最多的一个方法之一.但在很多时候,仅仅 ...

最新文章

  1. 【跃迁之路】【674天】程序员高效学习方法论探索系列(实验阶段431-2018.12.19)...
  2. 009_字符串内建函数
  3. spring的基本配置和使用
  4. Android 换肤demo,轻量快捷接入集成,判断是否夜间模式
  5. python中x y 1_Python的X[y==1, 0]
  6. atoi、stoi、strtoi区别
  7. ZigBee On Windows Mobile-ZigBee模块的设计制作
  8. 怎么证明自己会python_1024程序员节,请用一句话证明你是一个程序员!
  9. ospf多区域实例配置
  10. Node.js mm131图片批量下载爬虫1.01 增加断点续传功能
  11. Moodle安装教程以及phpMyAdmin无法访问解决
  12. 在项目中发现哪些经典bug?什么原因导致的?
  13. angular primeng 弹出对话框修改
  14. Cygwin的安装及csh的配置和使用(批量下载FNL数据方法)
  15. 详解项目管理中任务、成本、产品三者的关系
  16. 2022第十四届环泰山T60线上大徒步活动线下启动仪式圆满结束
  17. 查询出部门名称、部门的员工数、部门的平均工资、部门的最低收入雇员姓名和最高收入雇员的姓名
  18. 搜索引擎收录查询,是什么影响了网站被搜索引擎收录
  19. 【牛客网】邮票;python set()集合函数,去重;“”、“|”、“-”求并集、交集、差集
  20. 渐变折射率(GRIN)镜头的建模

热门文章

  1. 终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的
  2. 一文说通C#中的异步编程补遗
  3. Asp.Net Core多榜逆袭,这是.NET最好的时代!
  4. 开源netcore前后端分离,前端服务端渲染方案
  5. 前端 JS/TS 调用 ASP.NET Core gRPC-Web
  6. 为什么我会了SOA,你们还要逼我学微服务?
  7. 我,宇宙最强编辑器,支持远程开发
  8. C#如何安全、高效地玩转任何种类的内存之Memory(三)
  9. .NET Core完成向RyuJIT的迁移
  10. 【南京】.Net 开源基础服务线下技术交流会