范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

JavaSPI的原理和实践

  在Java中,我们经常会提到面向接口编程,这样减少了模块之间的耦合,更加灵活。
  在一个项目中我们也通常将接口和实现类放在一起,但是如果哪天我们要替换其它的实现类,或者是修改实现类,涉及到实现类的代码也要相应地修改。
  能不能这样:在调用服务的时候,我们只调用接口,不用关心实现类呢?无论我们怎么切换实现类,调用接口的部分代码都能正常运行?
  当然是可以的,Java SPI ( Service Provider Interface  )就提供了这样的机制。
  Java SPI机制中,我们 不再是手动指定接口和实现类的关系,而是让接口去寻找可用的实现类 。
  事实上,我们经常使用的Spring框架、日志接口等等,都是使用了SPI机制实现了扩展。 1,SPI和API
  在说起 SPI  之前,我们还是先看一下API  ,API  我们已经很熟悉了,和SPI  都可以被称作接口。
  只不过 API  的功能的实现,以及接口的定义全部是接口的实现者提供的,调用者只需要调用接口即可:
  不过 SPI  就不一样了,在SPI  机制中,调用者仍然是调用接口,但是这个接口是独立存在的,并且可以由不同的实现者实现:
  也就是说,这里接口只是一个标准,并且提供接口的那一方并不一定回去实现接口,而是根据接口的定义,由更多的第三方实现。
  这个接口可以 由一个甚至是多个实现者去实现 。也因此, 调用者在调用接口时,可能还需要指定一下使用哪个实现者的实现类 。
  实现者也叫做  服务提供者  。
  事实上,我们日常生活中经常使用的U盘也很类似SPI  机制,U盘使用的是USB接口,USB接口仅仅是一个规范(接口),但是发明USB接口的公司并没有去生产U盘,而是由不同的U盘厂商例如金士顿、闪迪(实现者)等等去根据这个规范生产U盘,然后我们就可以去选择自己喜欢的牌子(选择实现者)购买U盘,不过平时无论使用什么牌子的U盘,我们只需要插入到电脑的USB接口(调用接口)即可使用,而不用关心不同的厂商是怎么实现USB接口的功能的。
  可见,SPI  机制将实现者和接口再次解耦合了,使得接口更加易于扩展。
  事实上,我们常常用的SLF4J  就是一个Java的日志接口,但是它也仅仅是一个接口,所以被称作门面。而它的实现有Logback  、Log4j  等等,并且在切换实现的时候,我们只需要修改一下依赖配置即可,代码并不需要任何变动,因为代码中也仅仅是调用了接口。2,自己完成一个SPI
  那么现在,我们也来以一个最简单的日志接口为例,实现自己的SPI  。(1) 定义SPI接口
  先新建一个空的Maven项目log-interface  ,然后在里面创建一个日志接口,声明日志接口具备的方法(功能):package com.gitee.swsk33.loginterface.spi;  /**  * 定义日志接口  */ public interface Logger {  	/** 	 * INFO级别日志方法 	 * 	 * @param message 日志打印消息 	 */ 	void info(String message);  	/** 	 * DEBUG级别日志方法 	 * 	 * @param message 日志打印消息 	 */ 	void debug(String message);  } 复制代码
  这样,我们便定义了这么一个日志接口,并声明日志接口需要有info  和debug  这两个日志功能。
  然后就是编写服务类,这个服务类是这里最为重要的地方,它的作用是扫描所有实现了Logger  接口的实现类并加载进来,然后供调用者去调用。
  先看代码:package com.gitee.swsk33.loginterface.service;  import com.gitee.swsk33.loginterface.spi.Logger;  import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader;  /**  * 服务,用于加载所有服务使用者的实现类,以及供外部调用  * 该类为一个单例  */ public class LoggerService {  	/** 	 * 该类唯一单例 	 */ 	private static final LoggerService LOGGER = new LoggerService();  	/** 	 * 默认的Logger实现类 	 */ 	private final Logger defaultLogger;  	/** 	 * 所有的Logger实现类列表 	 */ 	private final List allLoggers = new ArrayList<>();  	/** 	 * 私有化构造器 	 */ 	private LoggerService() { 		// 加载全部Logger接口的实现类 		ServiceLoader loader = ServiceLoader.load(Logger.class); 		// 将实现类放入我们的Logger实现类列表 		for (Logger logger : loader) { 			allLoggers.add(logger); 		} 		// 这里取出第一个作为默认实现类 		if (!allLoggers.isEmpty()) { 			defaultLogger = allLoggers.get(0); 		} else { 			defaultLogger = null; 		} 		System.out.println("加载到" + allLoggers.size() + "个服务实现!"); 	}  	/** 	 * 获取该服务类的唯一单例 	 * 	 * @return 该服务类的唯一单例 	 */ 	public static LoggerService getInstance() { 		return LOGGER; 	}  	/** 	 * 调用默认的实现类的info日志打印方法 	 * 	 * @param message 消息 	 */ 	public void info(String message) { 		if (defaultLogger == null) { 			System.err.println("没有找到实现了Logger接口的类!"); 			return; 		} 		defaultLogger.info(message); 	}  	/** 	 * 调用默认的实现类的debug日志打印方法 	 * 	 * @param message 消息 	 */ 	public void debug(String message) { 		if (defaultLogger == null) { 			System.err.println("没有找到实现了Logger接口的类!"); 			return; 		} 		defaultLogger.debug(message); 	}  } 复制代码
  首先这个类是一个单例的类,在构造器中,我们使用ServiceLoader  这个类来将实现了Logger  接口的所有类都扫描进来,并存入我们的实现类列表,然后我们取出列表中的第一个作为默认实现。
  在下面我们定义了info  和debug  来完成对接口的默认实现类的调用。
  最后,在项目目录下执行mvn install  命令将其安装至本地Maven仓库,以便后续服务提供者引入并实现。(2) 完成一个接口的实现
  现在再新建一个空的Maven项目logservice-one  ,并引入上面接口项目为依赖:
  然后编写实现类:package com.gitee.swsk33.logserviceone.service;  import com.gitee.swsk33.loginterface.spi.Logger;  /**  * Logger SPI的实现类  */ public class LogOne implements Logger {  	@Override 	public void info(String s) { 		System.out.println("[LogOne INFO] " + s); 	}  	@Override 	public void debug(String s) { 		System.out.println("[LogOne DEBUG] " + s); 	}  } 复制代码
  然后在resources  目录下创建目录META-INF/services  ,这个目录中是用于声明该服务实现中有哪些实现类实现了什么接口。
  在这个目录下我们新建一个文件名为com.gitee.swsk33.loginterface.spi.Logger  ,文件中的内容为:com.gitee.swsk33.logserviceone.service.LogOne 复制代码
  可见,该目录下文件名是要实现的接口的全限定类名(包名 + 类名),而文件中内容是实现了该接口的实现类的全限定类名。
  大家参考这里的文件名及其中的内容,与我们上述的接口全限定类名、实现类全限定类名对比一下就知道了!
  如果说这个项目中有多个类实现了Logger  接口,那么我们都需要在文件中声明,一行一个实现类的全限定类名。
  最终整个项目结构如下:
  同样地,最后记得在项目目录下执行mvn install  命令将其安装至本地Maven仓库,以便调用者调用。(3) 测试接口
  这里再新建一个Maven空项目log-test  ,作为接口的调用者,在依赖中引入实现者:
  然后创建一个主类调用一下接口试试:package com.gitee.swsk33.logtest;  import com.gitee.swsk33.loginterface.service.LoggerService;  public class Main {  	private static final LoggerService LOGGER = LoggerService.getInstance();  	public static void main(String[] args) { 		LOGGER.info("测试info消息"); 		LOGGER.debug("测试debug消息"); 	}  } 复制代码
  结果:
  可见,我们成功地调用了Logger  接口中的方法。
  通常调用者的依赖中可能会同时引入  SPI  接口依赖和服务提供者(实现)的依赖,这样也没问题,不过通常服务提供者本身就依赖于SPI  接口,因此只引入服务提供者依赖,也会间接地引入SPI  接口依赖,不影响我们调用SPI  接口。
  我们这里只有一个服务提供者logservice-one  ,如果说还有logservice-two  等等多个服务提供者,我们只需要在依赖中更换一下即可,代码完全不需要改变。
  也可见调用者在调用接口的时候,只需要关注接口就行了,不需要关心实现类。3,再看ServiceLoader
  可见在SPI  接口中,我们使用ServiceLoader  完成了对所有实现了Logger  接口的类的扫描和加载,那么具体的过程是什么样的呢?
  如果大家去查看这个类的源码,可以发现它实现了Iterable  接口,这也说明我们可以通过迭代的方式去完成多个实现类的切换。
  然后在其源码中,有这么一个常量定义:static final String PREFIX = "META-INF/services/"; 复制代码
  这就说明,ServiceLoader  会去扫描服务提供者的classpath  路径下的META-INF/services  目录,来扫描哪些类实现了指定接口,而其静态方法load  的参数,正是指定了被实现的接口。也因此我们要在服务提供者的项目的resources  目录下创建这个目录并申明接口和对应实现类的全限定类名。
  在Maven项目中,  resources  目录就对应的是classpath  的根目录。
  简而言之,ServiceLoader  加载实现类的过程如下:先是调用load  方法并指定要扫描的接口然后扫描项目中META-INF/services  目录,这包括调用者项目以及它所引入的所有依赖包中的META-INF/services  目录下的声明扫描到所有实现类后,根据其类名,先判断是否跟SPI  接口为同一类型,如果是则利用反射的方式将所有实现类实例化,加载进内存,并返回所有实现类的实例列表
  可见,这就是JDK中SPI  机制加载服务的大致过程,事实上,现在很多框架也利用SPI  机制实现了灵活地扩展。

抖音开始征收费率,最高8本月起,抖音已开始对生活服务业务收取服务费。撰文张浩东出品支付百科近日,支付百科注意到,抖音在巨量学官网发布2022年生活服务软件服务费标准说明,明确6月1日起对生活服务业务收取服关注蔚来充放电一体机只租不卖被吐槽回应会快速提高产量文懂车帝原创彩丽美懂车帝原创行业6月6日,蔚来官方宣布,便携式充放电一体机正式发布。据了解,这款充放一体机可充可放,满足多场景需求。目前,该充放一体机仅支持通过官方渠道进行租用,并中视科技荣膺支付宝年度先锋合作伙伴1月12日,中视科技受邀参加在杭州举办的支付宝合作伙伴生态大会政企民生专场活动,并上台领取支付宝颁发的年度先锋合作伙伴奖!该奖项的获得,不仅是中视科技与支付宝双方在ETC及车生活领这家支付服务商连年亏损,现在退市了支付服务商你好现在发布公告在新三板退市。撰文里奥出品支付百科近日,支付服务商你好现在(北京)科技股份有限公司(以下简称你好现在)发布公告,拟申请股票在全国股转公司终止挂牌。并于20拿下中美科技金融之争跨越中等收入陷阱提高居民消费能力为什么会出现居民消费不足?居民消费不足的原因肯定是没钱呀!没钱的原因是收入不够丰厚不够稳定或者是住房支出过大!随着经济陷入相对低迷,呈U或V形结构导致了我国居民收入增速与消费不足的这两种水果,古人喜欢到写进诗里,美味又养生,六月正当季来源中国中医药报官方号来源中国中医药报云南中医文李幸入夏以后,水果纷纷上市,挑得人眼花缭乱。其中有两种水果,从古代起就倍受大家喜欢,还引发了许多趣事,今天就带大家了解一下它们的故事云南非常适合养老的几个宝藏地,北方人很喜欢的几个美丽地方听说北方人很多都喜欢到南方养老,南方人也有选择去北方养老。其实更多的是考虑到了交通消费环境气候等等,一系列相关因素。云南不但气候宜人物价不高(部分)底蕴深厚,因此好多人都选择在老的200万买狗,运费60万!赵大公子称自己能挣钱,全国各地开健身馆01hr近日,赵本山的儿子赵一楠因为花费200万买狗一事被大家议论,作为赵本山唯一的儿子,赵一楠自然是不缺钱的主,不过200万买条狗,就算是宠物狗,这消费还是超出大家预期的值得一提郭德纲粉丝不喜欢曹云金,觉得刘云天人不错,可惜像女人嫁错了郎郭德纲和曹云金之间的恩怨,想必很多德云社的粉丝都非常清楚。曹云金曾经是郭德纲最喜欢和看重的徒弟,曹云金在所有师兄弟中也确实是最有能耐的,相声风格最像郭德纲,水平也最接近郭德纲。因此从小网红到人气女星,白鹿为什么这么受人喜欢?三个原因白鹿在被于正签约之前,曾经是一个心里有着明星梦的网红小模特。也曾经参加过韩国练习生选秀,却被淘汰了。但她却在2016年遇见了影响自己一生的贵人于正,从而走上了演员之路。白鹿凭借自己不要驾照,2款外卖员喜欢骑的电动车,电池自由选,最高续航480km您在阅读前请点击上面的关注二字,后续会为您提供更多有价值的相关内容,感谢您的支持。新国标电动车不需要驾照就能上路,其他的电摩需要驾照才能骑行,这是新国标中已经明确的规定,但新国标电
摊牌大意了?郭艾伦失算,CBA19支球队集体无视,姚明也没有办法时间来到8月14日,CBA联盟处于休赛期,辽宁队球星郭艾伦还在追随国家队在欧洲拉练。距离郭艾伦向辽宁队摊牌申请交易的事情已经过去2周时间了,如今郭艾伦态度依旧坚决,辽宁队也重申绝对马宁有没有吹偏哨,2个镜头给到答案,球迷国安被51不是没原因北京国安和武汉三镇的比赛之后,当值主裁判马宁的吹罚引起了球迷的热议,认为他在吹偏哨。那么马宁到底有没有吹偏哨呢?2个镜头就能够给出答案。球迷评价国安被51不是没有原因。北京国安和武NBA一夜7大突发交易安东尼将重回掘金勇士决定放弃维金斯一安东尼将重回老东家掘金今天名记chru透露掘金准备签下安东尼,这让很多安东老街球迷热泪盈眶,在生涯的最后一个赛季能够在梦开始的地方结束职业生涯,这对于安东尼来说何尝不是一种幸福呢过人对抗全胜造绝杀机会,15分钟征服球迷!日本要出超级球星?布莱顿对阵纽卡的比赛,主角毫无疑问是波特与埃迪豪两大中生代顶级教头,波特用三年时间将布莱顿从保级区队伍打造成一支令曼城利物浦都头疼的强悍球队,而埃迪豪更是一上任就扭转乾坤,带领纽卡揭幕战敲定!湖人3换2豪赌欧文组三巨头!勇士争冠难了据TheAthletic名记Shams报道,202223赛季NBA揭幕战将在北京时间10月19日进行,卫冕冠军金州勇士主场对阵洛杉矶湖人。Shams报道,届时勇士众将将领取总冠军戒不开玩笑,我真的好喜欢武穴老城区如果城东新区的基调是光鲜亮丽的,那么老城区就是灰暗昏黄的,但即便如此,有一群人依旧爱着老城。他们留恋这里的记忆,喜欢这里的气息,今天,就带大家逛逛老城区吧!武穴老城区它是多彩且饱满速度与激情5电影精彩图片之五,电影图片带你看电影,喜欢吗速度与激情5电影精彩图片之五速度与激情5电影精彩图片之五速度与激情5电影精彩图片之五速度与激情5电影精彩图片之五速度与激情5电影精彩图片之五速度与激情5电影精彩图片之五速度与激情5一个人性格正常,却喜欢独来独往,多半是有这3种心理生活中,我们会遇到这样的人待人亲切有分寸,做事靠谱有效率,热爱生活有情趣,可他却不喜欢与人结伴而行,总是形单影只,对人也不多做解释,让周围人心中大为不解。人是具备社会属性的,没有人为什么自驾游老手都喜欢跑国道呢?自驾游旅行期间什么费用最高呢?自驾游的主要成本开支还是和车有关的,车的花费是最高的,也就是路桥费和油费最高,当然了土豪的自驾游不在这个范围之内。新藏线的219国道0公里处大家好我是文案总有一段故事,提起是遗憾,想起满是喜欢情字何解?怎落笔都不对,而我独缺你一生的了解委屈才会说睡了,你知道的。我从来不会早睡我将心事与你坦然诉说,结局我能猜中,但还在抱一丝希望后来,我不再遐想那道月光,只会在夕阳下会偶尔在勇士队,格林谈到他最喜欢汤普森的时刻克莱汤普森曾在三节内砍下60分,出于各种原因,这是他金州勇士队队友德拉蒙德格林最喜欢的时刻。早在2016年对阵步行者的比赛中,汤普森全场33投21中砍下60分,其中三分14投8中。稀有而神秘的巴塔哥尼亚冰龙,它生活在迅速消失的冰川上一条神秘的小生物生活在南美洲安第斯山脉的冰层中,如果你眨个眼,你可能会错过它。巴塔哥尼亚冰龙是一种长度超过半英寸(1。3厘米)的昆虫,它生活在冰川上。南巴塔哥尼亚冰原是一个淡水保护商汤发布AI下棋机器人元萝卜SenseRobot,进军C端消费市场2022年8月9日,北京全球领先的人工智能软件公司商汤科技召开新品发布会,重磅推出其首个家庭消费级人工智能产品元萝卜SenseRobotAI下棋机器人。该产品获得中国象棋协会权威认便宜电动小轿跑,2门4座硬顶设计,续航451km都开得起,零跑S01如今汽车市场是从燃油车到新能源汽车过度时期,在此期间,各大厂商绞尽脑汁,推出各式各样的汽车,各种动力组合而起,不断挖掘新的需求,新得汽车市场。其中最为成功例子,当属五菱宏光MINI从郁金香到数藏,不变的只有泡沫本质7月20日,一则消息震动了区块链行业国内最大的数字藏品平台幻核被传将关闭。完蛋了!这是在幻核投资了近5万元的小何的第一个想法,他马上去自己加入的数字藏品藏家群询问消息的真伪,群里已特斯拉被控虚假宣传或面临吊销汽车生产和销售执照最近特斯拉被加州有关部门指控其自动驾驶相关功能涉嫌虚假宣传。特斯拉自动驾驶技术加州车管局称,特斯拉在其网站上虚假宣传其自动驾驶仪和完全自动驾驶(FSD)功能。此外,特斯拉首席执行官3大工具助你叱咤保险互联网2。0时代近两年保险从业人员真正重新洗牌,我相信这是大家都能切身感受到的,科技在发展,时代在改变,保险互联网2。0的时代正在降临。之前我说,站在时代的风口上,什么都有可能发生。展业模式在改变百度将在自动驾驶领域发威,至少领先特斯拉汽车一代前言8月8日,在集度首届汽车机器人生态伙伴大会上,百度创始人董事长兼首席执行官李彦宏以视频连线的方式发表现场演讲,李彦宏表示对于智能汽车,我们的判断是,电动化是中场,智能化是终局。五菱新能源第一个100万达成在新能源这场赛道上,五菱汽车在不断创造佳绩。8月8日,五菱汽车官微发布消息,五菱首款新能源全球车AirEV(右舵版)在印尼正式下线,标志着五菱新能源汽车正式突破100万辆。同时,A将安全进行到底!奥特能平台专注行车安全,保障每一次出行近年来,国家关于低碳出行的相关政策持续出台,油价也连番上涨,驾驶新能源汽车实现低碳出行似乎已经逐渐成为了人们生活的全新出行选择之一。相比燃油车,新能源汽车主要能量来源是高压动力电池从造电池到造汽车再到世界500强,比亚迪为何发展如此迅速?8月3日,一个微博热搜引起我的注意比亚迪入榜世界500强一共24家整车企业上榜,中国汽车企业占据8席,比亚迪名列其中。在同一天,比亚迪发布了7月份销售数据,汽车销量已达到16。2万Redis实现分布式锁的7种方案,及正确使用姿势种方案前言日常开发中,秒杀下单抢红包等等业务场景,都需要用到分布式锁。而Redis非常适合作为分布式锁使用。本文将分七个方案展开,跟大家探讨Redis分布式锁的正确使用方式。如果有