【java编程】ServiceLoader使用看这一篇就够了
转载:https://www.jianshu.com/p/7601ba434ff4
想必大家多多少少听过spi,具体的解释我就不多说了。但是它具体是怎么实现的呢?它的原理是什么呢?下面我就围绕这两个问题来解释:
实现: 其实具体的实现类就是java.util.ServiceLoader这个类。
要想了解一个机制的原理,首先得知道它是怎么运行的,需要什么配置,才能运行起来。然后再分解来了解实现。对于技术实现也是一样,先看这个类是怎么实现的,先让它跑起来,看到效果。然后再讲原理。
按照使用说明文档,应该分下面几个步骤来使用:
- 创建一个接口文件
- 在resources资源目录下创建META-INF/services文件夹
- 在services文件夹中创建文件,以接口全名命名
- 创建接口实现类
我们想测试一下,一般是在这个工程中建立一个测试类来测试。来看下代码片段:
接口类
public interface IMyServiceLoader {
String sayHello();
String getName();
}
实现类:
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";
}
}
测试类:
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());
}
}
}
正常情况下这里应该输出
name2hello2
name1hello1
看了这些步骤,想必你也知道原理了,我在这里总结下。
原理:在ServiceLoader.load的时候,根据传入的接口类,遍历META-INF/services目录下的以该类命名的文件中的所有类,并实例化返回。
相信看到这里,有的看客该爆粗话了,说啥子看着一篇就够了,这些知识点随便一搜,到处都是好伐。是的,上面说的,确实随便一搜都可以搜到,所以这里我要划重点了:
一、问题
上面说了,正常情况下会那样输出,但是你运行程序你就会发现,马丹,怎么不起作用啊,我哪里做错了,都是按照文章步骤来做的。弄的你都开始怀疑人生了。不要怀疑人生,在一个工程中做测试,确实不能实现想要的效果。
二、回忆场景
回忆一下spi的使用场景。它是给制作标准的一放用的,用来指定标准,然后不同实现方,用不同的方式实现标准供使用方使用。那标准方和实现方必然不是一个。想到这里,你应该能够向明白了吧。
三、解决方案
要解决问题,就把之前做的打jar包,引入新工程测试,这样就可以了。
四、疑问
但是有人会说标准方和实现方也可能是一个啊,好比标准方我提供一个内部的实现方案也是可以的啊。也确实有道理啊,那这种怎么实现呢?
五、思考
六、疑问临时解决方案
最简单的方法,把资源下的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;
}
这里你会发现其实就是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);
}
}
确实如此,这里会把所有的实现类存起来。
再来看看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;
}
}
}
这里是在build下创建META-INF目录。和我们想的一模一样。
八、总结
好了,要实现文章开头的需求,除非你觉得你比google开源AutoService的工程师写的更好,不然就直接使用AutoService吧。这篇文章不仅是分析ServiceLoader的原理,实现我们的需求,更重要的是高速我们遇到问题该怎么分析问题,解决问题。
九、扩展
其实还有很多比较好玩的,比如在拦截到编译过程时,可以再编译期生成一些有意思的代码,来帮我们实现一些自动化处理。这就需要动用我们的大脑就想了,介绍一下生成代码的库javapoet,大家可以了解一下。
【java编程】ServiceLoader使用看这一篇就够了的更多相关文章
- Java并发编程入门,看这一篇就够了
Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容.这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中涉 ...
- Java中的多线程=你只要看这一篇就够了
如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...
- [转帖]nginx学习,看这一篇就够了:下载、安装。使用:正向代理、反向代理、负载均衡。常用命令和配置文件
nginx学习,看这一篇就够了:下载.安装.使用:正向代理.反向代理.负载均衡.常用命令和配置文件 2019-10-09 15:53:47 冯insist 阅读数 7285 文章标签: nginx学习 ...
- 关于 Docker 镜像的操作,看完这篇就够啦 !(下)
紧接着上篇<关于 Docker 镜像的操作,看完这篇就够啦 !(上)>,奉上下篇 !!! 镜像作为 Docker 三大核心概念中最重要的一个关键词,它有很多操作,是您想学习容器技术不得不掌 ...
- JVM内存模型你只要看这一篇就够了
JVM内存模型你只要看这一篇就够了 我是一只孤傲的鱼鹰 让我们不厌其烦的从内存模型开始说起:作为一般人需要了解到的,JVM的内存区域可以被分为:线程栈,堆,静态方法区(实际上还有更多功能的区域,并且这 ...
- 鸿蒙应用程序Ability(能力)看这一篇就够
本节概述 什么是Ability Ability分类 Ability生命周期 Ability之间跳转 什么是Ability Ability意为能力,是HarmonyOS应用程序提供的抽象功能.在Andr ...
- 什么是 DevOps?看这一篇就够了!
本文作者:Daniel Hu 个人主页:https://www.danielhu.cn/ 目录 一.前因 二.记忆 三.他们说-- 3.1.Atlassian 回答"什么是 DevOps?& ...
- 2019-5-25-win10-uwp-win2d-入门-看这一篇就够了
title author date CreateTime categories win10 uwp win2d 入门 看这一篇就够了 lindexi 2019-5-25 20:0:52 +0800 2 ...
- windows server 2019 域控批量新增不用,只看这一篇就够了,别的不用看
windows server 2019 域控批量新增不用,只看这一篇就够了,别的不用看 1. 新建excel表格 A B C D E 姓 名 全名 登录名 密码 李 四 李四 李四 test123!@ ...
随机推荐
- python介绍与入门
一.python 的介绍 python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为 ...
- day043 前端css样式
标签嵌套规则 块级标签能够嵌套某些块级标签和内敛标签(行内标签) 内敛标签不能块级标签,只能嵌套内联标签 块级标签能够设置高度和宽度 内敛标签不能设置,设置没有效果 Css样式 高度宽度: Width ...
- Jenkins详细安装与构建部署使用教程
版权声明:本文为博主林炳文Evankaka原创文章,转载请注明出处http://blog.csdn.net/evankaka 目录(?)[+] Jenkins是一个开源软件项目,旨在提供一个开 ...
- 接触到的加密算法MD5、SHA1(转)
参考链接: https://blog.csdn.net/u012611878/article/details/54000607 https://blog.csdn.net/worm0527/artic ...
- 循环神经网络-LSTM
LSTM(Long Short-Term Memory)是长短期记忆网络,是一种时间递归神经网络,适合于处理和预测时间序列中间隔和延迟相对较长的重要事件. LSTM能够很大程度上缓解长期依赖的问题. ...
- day 67 django 之ORM 增删改查基础
一 操作基础前提准备 1. 新建django 项目 mysite 子项目app01 ,选择好做路径. 2 .2-1在app01 下面models 中引用 模块 from django.db im ...
- 下载从网页里面提取出来的图片(将url指向的图片下载并保存、从命名)
import os #创建文件夹 from urllib import request #下载图片 if not os.path.exists('文件夹名字'): #创建文件夹名字 os.mkdir( ...
- Python Counter
from collections import Counter print(Counter("宝宝今年特别喜欢王宝强")) # 计数 lst = ["jay", ...
- puppet确保程序运行
exec { 'keep-nginx-running' : user => 'root', unless => 'ps -x | grep nginx|grep -v grep', com ...
- 最新2018年三月可用Windows10激活密钥
Windows 10 Edition Product Key Windows 10 Home TX9XD-98N7V-6WMQ6-BX7FG-H8Q99 Windows 10 Home Single ...