应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

从实现到原理,聊聊Java中的SPI动态扩展

  1、简介
  SPI的全称是ServiceProviderInterface,翻译过来就是服务提供者的接口,它所实现的其实是一种服务的发现机制。
  这么说起来可能还是有点不好理解,我举个例子来类比一下。
  在spring项目中,写service层代码前,会约定俗成的会添加一个接口层。然后通过spring中的依赖注入,可以借助Autowired等方式注入这个接口的实现类的实例对象,之后对于service的调用一般也基于接口操作。
  简单形容就是这样的:
  如图所示,接口、实现类都是由服务提供方提供,我们可以把controller看作服务调用者,调用方只管调用接口就可以了。
  虽然也有声音认为,大部分情况下service只有一个实现类,接口层显得有些多余。但是在《HeadFirstDesignPatterns》这本书中,大佬们还是建议过:
  Programtoaninterface,notanimplementation。
  没错,就是常说的要面向接口编程。至于好处,也不外乎是降低耦合度、方便日后扩展、提高了代码的灵活性和可维护性等等。
  在上面这个例子里,这个接口层和其中的方法我们可以称之为API,而我们要讨论的SPI和它相比,有类似也有差异,还是先看图:
  简单来说,就是服务的调用方定义一个接口规范,可以由不同的服务提供者实现。并且,调用方能够通过某种机制来发现服务提供方,并通过接口调用它的能力。
  通过对比,我们可以看出它们虽然都有着接口这一层面,但还是有很大的不同:
  API中的接口是服务提供者给服务调用者的一个功能列表,而SPI中更多强调的是,服务调用者对服务实现的一种约束,服务提供者根据这种约束实现的服务,可以被服务调用者发现。
  说白了,Java中的SPI实现的就是,你按我的接口规范实现服务,我就能通过某种机制为这个接口寻找到这个服务。
  这么说起来可能还有些抽象,下面我们举一个例子,类比具体描述一下这个过程。2、定义接口
  说起智能家居系统,大家现在都比较熟悉了,只要是相同品牌下的产品,连上wifi就能够通过手机app控制了,非常方便。
  虽然产品不断更新换代,型号更新层出不穷,但是同种家电在app上操作起来,功能一般都是一样的。就拿空调来说,我们在app上操作起来一般也就三个主要功能:开关,选模式,调节温度。
  假设我现在在客厅、卧室、书房安装了3款不同型号的空调,并把它们都接入到了我app中,那么之后的操作都是相同的几个按键,简单粗暴。
  思考一下,无论是开关还是调温,都是通过app去调用设备的接口罢了,那么如果不同型号的空调各写各的接口,后端app在开发的时候光对接接口都麻烦的要死。
  解决方法也很简单,我先定义一套接口规范,不管你以后什么型号的空调,都按我的规范来实现接口。以后只要我能发现你的设备,那么都可以按相同的方法来调用接口。
  那么下面就先来定义这么一套接口规范,如果你以后想要接入智能家居系统,那么就要遵循这个规范来开发接口。
  新建一个项目作为标准,就叫airconditionstandard好了,然后创建一个接口。除了3个操作以外,我们再添加一个获取空调型号的方法。publicinterfaceIAircondition{获取型号StringgetType();开关voidturnOnOff();调节温度voidadjustTemperature(inttemperature);模式变更voidchangeModel(intmodelId);}
  这个接口后面要给服务的实现方来使用,用maven把它打成jar包:mvncleaninstall
  之后服务提供者在项目中就可以引入这个jar包了,有了这套规范,就保证了产品后期不管怎么更新换代,都能接入到系统来。3、服务实现
  制定并发布完规则后,挂式空调作为第一个服务提供者就来了,新建一个项目airconditionhangingtype,并引入刚才打好的jar包:dependencygroupIdcom。cn。hydragroupIdairconditionstandardartifactIdversion1。0SNAPSHOTversiondependency
  创建服务类,并实现前面定义的接口:publicclassHangingTypeAirconditionimplementsIAircondition{publicStringgetType(){returnHangingType;}publicvoidturnOnOff(){System。out。println(挂式空调开关);}publicvoidadjustTemperature(inti){System。out。println(挂式空调调节温度);}publicvoidchangeModel(inti){System。out。println(挂式空调更换模式);}}
  在项目的resources的目录下,创建METAINFservices目录,然后以前面定义的接口名com。cn。hydra。IAircondition创建文件,并在文件中写入实现类的全限定名。com。cn。hydra。HangingTypeAircondition
  整个项目结构非常简单:
  这样,一个服务方的简单实现就搞定了,用maven打成jar包,之后就可以提供给调用方使用了。
  同理,我们可以再创建一个立式空调的项目airconditionverticaltype,也只创建一个服务类:publicclassVerticalTypeAirconditionimplementsIAircondition{publicStringgetType(){returnVerticalType;}publicvoidturnOnOff(){System。out。println(立式空调开关);}publicvoidadjustTemperature(inti){System。out。println(立式空调调节温度);}publicvoidchangeModel(inti){System。out。println(立式空调更换模式);}}
  还是按上面的命名规则,创建一个配置文件:com。cn。hydra。VerticalTypeAircondition
  同样,打成jar包就完事了,至于服务调用者如何去发现和调用这两个服务,下面详细再说。4、服务发现
  现在两个服务提供方都实现了接口,下面关键的一步就是服务发现,这一步java中的spi发现机制已经帮我们实现好了。
  创建一个新项目airconditionapp,引入上面打好的两个jar包。dependenciesdependencygroupIdcom。cn。hydragroupIdairconditionhangingtypeartifactIdversion1。0SNAPSHOTversiondependencydependencygroupIdcom。cn。hydragroupIdairconditionverticaltypeartifactIdversion1。0SNAPSHOTversiondependencydependencies
  按照上面的说法,虽然每个服务提供者对于接口都有不同的实现,但是作为调用者来说,它并不需要关心具体的实现类,我们要做的是通过接口来调用服务提供者实现的方法。
  下面,就是关键的服务发现环节,我们写一个方法,根据型号去调用对应空调的开关方法。publicclassAirconditionApp{publicstaticvoidmain(String〔〕args){newAirconditionApp()。turnOn(VerticalType);}publicvoidturnOn(Stringtype){ServiceLoaderIAirconditionloadServiceLoader。load(IAircondition。class);for(IAirconditioniAircondition:load){System。out。println(检测到:iAircondition。getClass()。getSimpleName());if(type。equals(iAircondition。getType())){iAircondition。turnOnOff();}}}}
  测试结果:
  可以看到,测试过程中,通过定义的接口IAircondition发现了两个实现类,并通过参数,调用了特定实现类的某个方法。整段代码中没有出现过具体的服务实现类,操作都是通过接口调用。5、原理
  了解了spi的工作流程,我们再来看看它的实现,其实最关键的就是上面代码中出现的ServiceLoader这个类。
  上面的示例代码中,对于ServiceLoader的load()方法的结果,我们用for循环进行了遍历,这一点我们看一下源码就能明白,因为ServiceLoader实现了Iterable这一接口,而整个服务发现的核心,就在它的iterator()方法中。
  注意这里面有两个关键的东西,找一下在源码中定义的地方:
  注释写的非常明白,providers就是一个缓存,在迭代器中如果先从这里面进行查找,如果里面有就继续往下找,没有了的话就用这个懒加载的lookupIterator查找。
  那么就简单了,接着往下看LazyIterator,看看它里面的hasNext()和next()两个方法是怎么实现的。
  这个acc是一个安全管理器,在前面通过System。getSecurityManager()判断并赋值,debug看一下这里都是null,所以直接看hasNextService()和nextService()方法就可以了。
  在hasNextService()方法中,会取出接口取出实现类的类名放到nextName中:
  接下来,在nextService()方法中,则会先加载这个实现类,然后实例化对象,最终放入缓存中去。
  在迭代器的迭代过程中,会完成所有实现类的实例化,其实归根结底,还是基于java反射去实现的。6、应用
  要说spi的实际应用,大家最常见的应该就是日志框架slf4j了,它利用spi实现了插槽式接入其他具体的日志框架。
  说白了,slf4j本身就是个日志门面,并不提供具体的实现,需要绑定其他具体实现才能真正的引入日志功能。
  例如我们可使用log4j2作为具体的绑定器,只需要在pom中引入slf4jlog4j12,就可以使用具体功能。dependencygroupIdorg。slf4jgroupIdslf4japiartifactIdversion2。0。3versiondependencydependencygroupIdorg。slf4jgroupIdslf4jlog4j12artifactIdversion2。0。3versiondependency
  引入项目后,点开它的jar包看一下具体结构:
  有没有发现一个彩蛋,先说为什么我们pom中引入的明明是slf4jlog4j12,实际上引入的是slf4jreload4j?翻一下官网的文档:
  大意就是在2015年和2022年,log4j1。x就已经宣布endoflife终止了,原因也不难猜,估计是因为频繁爆出的漏洞。在那之后,slf4jlog4j在构建阶段就会自动重定向到slf4jreload4j了,并且官方也强烈建议使用slf4jreload4j作为替代。
  再回头看一下jar包的METAINF。services里面,通过spi注入了Reload4jServiceProvider这个实现类,它实现了SLF4JServiceProvider这一接口,在它的初始化方法initialize()中,会完成初始化等工作,后续可以继续获取到LoggerFactory和Logger等具体日志对象。7、总结
  Java中的SPI提供了一种比较特别的服务发现和调用机制,通过接口灵活的将服务调用与服务提供者分离,用于提供给第三方实现扩展时还是很方便的。但是也有缺点,比方说一旦加载一个接口,就会把所有实现类都加载进来,可能会加载到不需要的冗余服务。不过站在整体角度上,还是给我们提供了一种非常不错的框架扩展、集成的思路。

53投101分!感谢东契奇,感谢塔图姆,你们逼出了一个NBA本赛季有多个年轻人打出了炸裂级表现,其中以东契奇、塔图姆的表现最为耀眼,很多人都说本赛季的MVP可能在这2人中产生。东契奇目前场均能轰下34。3分8。7板8。1助攻2抢断0。7……咳嗽(痰热郁肺证)中医之自我辩证之四咳嗽是指肺失肃降,肺气上逆作声,咳出痰液而言,为肺系疾病的主要证候之一。分别言之,有声无痰为咳,有痰无声为嗽,一般多为痰声并见,难以截然分开,故以咳嗽并称。咳嗽既是独立性的病证……听说人生有三苦放不下忘不了舍不得放不下人生有很多放不下的人和事,有太多的羁绊和遗憾,所以很多人总是做不到洒脱。昨天和朋友聊天,他说希望俄罗斯挑起第三次世界大战,这样普通人才有发展的机会。我说……有中国公民在伊朗因拍摄不当视频和敏感设施被判刑,中国使馆提醒据中国驻伊朗大使馆消息,近期,到伊朗临时出差或旅游的中国公民逐渐增多。使馆注意到,有的自媒体博主自曝感染新冠肺炎后仍坚持旅行,有的因围观和拍摄伊朗社会治安事件而被伊警方扣押。去……临阵换将!阿莱士桑德罗因伤退出巴西队,蒂特补招雷南洛迪在早先公布的巴西队大名单中,主教练蒂特征召了特莱斯和阿莱士桑德罗这两名左后卫,可在集训开始前,巴西队不得不临阵换将据巴西足协透露:尤文边后卫阿莱士桑德罗因左大腿肌肉拉伤退出集训……莫桑石十字架项链的含义你知道吗?颈链是一种很常见的首饰,各种各样的颈链更是比比皆是。想必大家都看到过十字架项链,但十字架项链的含义是什么呢?十字架是远古传说就已存在的象征物,因而十字架项链的含义也多种多……每日元速递丨海尔智家官宣数字人AYAYI和锘亚京东方布局元宇01。中移动跨元宇宙物品处理专利公布,可实现跨虚拟世界交易据天眼查显示,咪咕文化科技有限公司、中国移动通信集团有限公司跨元宇宙物品处理方法、装置、设备和存储介质专利公布。……莫科快哭了,四川男篮顶级中锋状态下滑,他才30岁就将离开CB莫科快哭了,四川男篮顶级中锋状态下滑,他才30岁就将离开CBA?CBA新赛季已经开赛一周有余,从最近五轮比赛的赛果基本上对各支球队也有了一个新的认识,就整个积分榜来说,除了上海……入伏后喝什么茶好今天入伏,入伏后喝什么茶好?入伏后喝什么茶有讲究吗?为什么反而建议三伏天喝一点发酵茶?最近全国的天气太疯狂了,很多温度都是到了38度,局部的室外温度有的是50多度,达到了……34岁李晓霞正式走马上任!新马尾造型显年轻,越来越有女人味近日,让国人引以为傲的国乒再度传来一个好消息,前国乒大满贯得主、原女队队长李晓霞被赋予了新的使命。在刘国梁和中国乒协的推举下,李晓霞被国际乒联聘为了专家,加入到了其下属的世界排……难怪饭店的凉拌海带好吃,原来有窍门,教做法,爽脆味道香说起大众都喜欢的凉拌菜,凉拌海带必定是其中一种。海带清爽且自带鲜味,适合的烹饪方式很多,不管是烹煮还是煎炒,都可口美味,春节买年货的时候,实惠好吃的海带几乎是一个必选项。小时候……队内最高薪水不超过2000万美元,这5支球队誓要抢文班亚马?NBA季后赛开打已3个比赛日,随着76人和湖人两连败,登顶东西部班长的席位,其他几支专业摆烂球队就坐不住了。先说下联盟的薪水王,自然是上赛季冠军勇士队的史蒂芬库里,他这个……
球迷建议湖人来一个大胆的三方交易,送走安东尼戴维斯和威少洛杉矶湖人队在休赛期要做出一些艰难的决定。如果湖人想要马上成为一支具备争夺NBA总冠军球队的实力和状态,那么他们必须做出一些令人震惊和激烈的事情,更换教练就是其中之一。在……斯诺克世锦赛首个八强出炉,中英磨功大战即将打响北京时间4月22日晚上,斯诺克世锦赛首个八强诞生,首个阶段7:1领先杰克逊佩奇的七五三杰之马克威廉姆斯在第二阶段以13:3送自己的小老乡大黑马佩奇回家。纵观全局,状态火热的威廉……夏日缺水身体有这反应更会加速衰老大家都知道每天喝8杯水就能满足身体所需的水分,维持身体健康。不过,要一天喝足8杯水并不容易,特别是不喜欢喝水或忙碌的人,有时候真的一天也喝不到一杯水。其实不要看轻饮水,因为身体……好消息!国内油价今晚或迎来4连跌,92号汽油一箱再省5元好消息!近期国际油价持续跌跌不休,随着国际油价的跌落,国内油价也将在7月的三连跌之后,于今晚再度迎来四连跌!我国成品油的新一轮调价窗口将会于8月9日的24点打开。据业内机……周杰伦发文晒时髦妈妈,网友戏称被儿子耽误的时尚博主周杰伦在社交平台发文晒妈妈日常穿搭,称妈妈比我还潮怎么回事,照片看起来周董妈妈真的很会穿,虽满头白发,但不失端庄时尚,中式黑色小西服搭配同色系礼帽,非常有气场。网友称:叶惠美是……我娃6岁撒的人生第1谎,教我撒谎还有好处,教育要活学活用文章快读:〔玫瑰〕一有时撒谎对人有利,真话却很麻烦。〔玫瑰〕二孩子撒谎的四个好处。〔玫瑰〕三我处理孩子撒谎的故事。一孩子和傻子不会撒谎真的吗?有句……油价强势反弹超4,节后调整9月美油累计下跌约11,布油累跌约8。8,均为连续第四个月下跌。三季度跌超20,而10月开市第一日,国际油价强势反弹超4。国内成品油零售价将于10月10日24时迎来第19……与铁路人相爱,是一种什么样的体验?今天是七夕节在这氤氲着爱情气息的传统节日有心动,有陪伴,有表达万千故事里爱情的样子不会只有一种定义今天小编带大家看一看那些与铁路人相爱……红米note11t消息,增加一个8512g内存的版本红米note11t消息,这次有512g内存的版本?最高配是要使用120瓦的快充,使用lcd屏幕,最高支持144赫兹的屏幕刷新率。使用天玑8000处理器。最近手机内存增加了……春季脸部皮肤太干燥了严重缺水怎么办用什么补水最快最有效?(精早上起床照镜子,发现最近皮肤特别干燥,涂抹完水以后脸刺疼,甚至涂完粉底以后,脸还会起皮,大部分人会单纯的认为是皮肤缺水造成的,开美容院很多年了,今天就码一篇文章说一说这个问题吧……因为闹离婚,他们废掉了市值900多亿的公司在商界中,有很多夫妻档。医药首富孙飘扬和钟慧娟,甚至在同一个行业中,却各自发展出顶级医药公司,成为令人欣羡的存在,相较于各自发展,更多的是夫唱妇随,共同打拼。然而,……科学家称终于发现历史上最大的气候灾难的触发因素据BGR报道,科学家们认为他们终于发现了世界上最大的气候灾难的触发因素。这场灾难发生在大约2。52亿年前。当时,世界正在经历一个动荡不安的全球快速变暖期。许多人以前认为,……
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网