生活工程体验信仰哲学精神
投稿投诉
精神世界
探索历史
哲学文学
艺术价值
信仰创造
境界审美
体验技术
技能工具
工程信息
医学生产
生活运用
操作能力

还不会JavaSPI机制?

1月21日 火凤派投稿
  什么是SPI1。背景
  在面向对象的设计原则中,一般推荐模块之间基于接口编程,通常情况下调用方模块是不会感知到被调用方模块的内部具体实现。一旦代码里面涉及具体实现类,就违反了开闭原则。如果需要替换一种实现,就需要修改代码。
  为了实现在模块装配的时候不用在程序里面动态指明,这就需要一种服务发现机制。JavaSPI就是提供了这样一个机制:为某个接口寻找服务实现的机制。这有点类似IOC的思想,将装配的控制权移交到了程序之外。
  SPI英文为ServiceProviderInterface字面意思就是:服务提供者的接口,我的理解是:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。
  SPI将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。2。使用场景
  很多框架都使用了Java的SPI机制,比如:数据库加载驱动,日志接口,以及dubbo的扩展实现等等。3。SPI和API有啥区别
  说到SPI就不得不说一下API了,从广义上来说它们都属于接口,而且很容易混淆。下面先用一张图说明一下:
  一般模块之间都是通过通过接口进行通讯,那我们在服务调用方和服务实现方(也称服务提供者)之间引入一个接口。
  当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是API,这种接口和实现都是放在实现方的。
  当接口存在于调用方这边时,就是SPI,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务,举个通俗易懂的例子:公司H是一家科技公司,新设计了一款芯片,然后现在需要量产了,而市面上有好几家芯片制造业公司,这个时候,只要H公司指定好了这芯片生产的标准(定义好了接口标准),那么这些合作的芯片公司(服务提供者)就按照标准交付自家特色的芯片(提供不同方案的实现,但是给出来的结果是一样的)。
  实战演示
  Spring框架提供的日志服务SLF4J其实只是一个日志门面(接口),但是SLF4J的具体实现可以有几种,比如:Logback、Log4j、Log4j2等等,而且还可以切换,在切换日志具体实现的时候我们是不需要更改项目代码的,只需要在Maven依赖里面修改一些pom依赖就好了。
  这就是依赖SPI机制实现的,那我们接下来就实现一个简易版本的日志框架。1。ServiceProviderInterface
  新建一个Java项目serviceproviderinterface目录结构如下:。ideasrcMETAINForgspiserviceLogger。javaLoggerService。javaMain。javaMyServicesLoader。java
  新建Logger接口,这个就是SPI,服务提供者接口,后面的服务提供者就要针对这个接口进行实现。packageorg。spi。publicinterfaceLogger{voidinfo(Stringmsg);voiddebug(Stringmsg);}
  接下来就是LoggerService类,这个主要是为服务使用者(调用方)提供特定功能的。如果存在疑惑的话可以先往后面继续看。packageorg。spi。importjava。util。ArrayLimportjava。util。Limportjava。util。ServiceLpublicclassLoggerService{privatestaticfinalLoggerServiceSERVICEnewLoggerService();privatefinalLprivatefinalListLoggerloggerLprivateLoggerService(){ServiceLoaderLoggerloaderServiceLoader。load(Logger。class);ListLoggerlistnewArrayList();for(Loggerlog:loader){list。add(log);}LoggerList是所有ServiceProviderloggerLif(!list。isEmpty()){Logger只取一个loggerlist。get(0);}else{}}publicstaticLoggerServicegetService(){returnSERVICE;}publicvoidinfo(Stringmsg){if(loggernull){System。out。println(info中没有发现Logger服务提供者);}else{logger。info(msg);}}publicvoiddebug(Stringmsg){if(loggerList。isEmpty()){System。out。println(debug中没有发现Logger服务提供者);}loggerList。forEach(loglog。debug(msg));}}
  新建Main类(服务使用者,调用方),启动程序查看结果。packageorg。spi。publicclassMain{publicstaticvoidmain(String〔〕args){LoggerServiceserviceLoggerService。getService();service。info(HelloSPI);service。debug(HelloSPI);}}
  程序结果:
  info中没有发现Logger服务提供者
  debug中没有发现Logger服务提供者
  将整个程序直接打包成jar包,可以直接通过IDEA将项目打包成一个jar包。
  2。ServiceProvider
  接下来新建一个项目用来实现Logger接口
  新建项目serviceprovider目录结构如下:。idealibserviceproviderinterface。jarsrcMETAINFservicesorg。spi。service。LoggerorgspiproviderLogback。java
  新建Logback类packageorg。spi。importorg。spi。service。LpublicclassLogbackimplementsLogger{Overridepublicvoidinfo(Stringmsg){System。out。println(Logbackinfo的输出:msg);}Overridepublicvoiddebug(Stringmsg){System。out。println(Logbackdebug的输出:msg);}}
  将serviceproviderinterface的jar导入项目中。新建lib目录,然后将jar包拷贝过来,再添加到项目中。
  再点击OK。
  接下来就可以在项目中导入jar包里面的一些类和方法了,就像JDK工具类导包一样的。
  实现Logger接口,在src目录下新建METAINFservices文件夹,然后新建文件org。spi。service。Logger(SPI的全类名),文件里面的内容是:org。spi。provider。Logback(Logback的全类名,即SPI的实现类的包名类名)。
  这是JDKSPI机制ServiceLoader约定好的标准
  接下来同样将serviceprovider项目打包成jar包,这个jar包就是服务提供方的实现。通常我们导入maven的pom依赖就有点类似这种,只不过我们现在没有将这个jar包发布到maven公共仓库中,所以在需要使用的地方只能手动的添加到项目中。3。效果展示
  接下来再回到serviceproviderinterface项目。
  导入serviceproviderjar包,重新运行Main方法。运行结果如下:
  Logbackinfo的输出:HelloSPI
  Logbackdebug的输出:HelloSPI
  说明导入jar包中的实现类生效了。
  通过使用SPI机制,可以看出服务(LoggerService)和服务提供者两者之间的耦合度非常低,如果需要替换一种实现(将Logback换成另外一种实现),只需要换一个jar包即可。这不就是SLF4J原理吗?
  如果某一天需求变更了,此时需要将日志输出到消息队列,或者做一些别的操作,这个时候完全不需要更改Logback的实现,只需要新增一个服务实现(serviceprovider)可以通过在本项目里面新增实现也可以从外部引入新的服务实现jar包。我们可以在服务(LoggerService)中选择一个具体的服务实现(serviceprovider)来完成我们需要的操作。
  loggerList。forEach(loglog。debug(msg));
  或者
  loggerList。get(1)。debug(msg);
  loggerList。get(2)。debug(msg);
  这里需要先理解一点:ServiceLoader在加载具体的服务实现的时候会去扫描所有包下src目录的METAINFservices的内容,然后通过反射去生成对应的对象,保存在一个list列表里面,所以可以通过迭代或者遍历的方式得到你需要的那个服务实现。
  3。ServiceLoader
  想要使用Java的SPI机制是需要依赖ServiceLoader来实现的,那么我们接下来看看ServiceLoader具体是怎么做的:
  ServiceLoader是JDK提供的一个工具类,位于packagejava。包下。Afacilitytoloadimplementationsofaservice。
  这是JDK官方给的注释:一种加载服务实现的工具。
  再往下看,我们发现这个类是一个final类型的,所以是不可被继承修改,同时它实现了Iterable接口。之所以实现了迭代器,是为了方便后续我们能够通过迭代的方式得到对应的服务实现。publicfinalclassServiceLoaderSimplementsIterableS{xxx。。。}
  可以看到一个熟悉的常量定义:
  privatestaticfinalStringPREFIXMETAINF
  下面是load方法:可以发现load方法支持两种重载后的入参;publicstaticSServiceLoaderSload(ClassSservice){ClassLoaderclThread。currentThread()。getContextClassLoader();returnServiceLoader。load(service,cl);}publicstaticSServiceLoaderSload(ClassSservice,ClassLoaderloader){returnnewServiceLoader(service,loader);}privateServiceLoader(ClassSsvc,ClassLoadercl){serviceObjects。requireNonNull(svc,Serviceinterfacecannotbenull);loader(clnull)?ClassLoader。getSystemClassLoader():acc(System。getSecurityManager()!null)?AccessController。getContext():reload();}publicvoidreload(){providers。clear();lookupIteratornewLazyIterator(service,loader);}
  根据代码的调用顺序,在reload()方法中是通过一个内部类LazyIterator实现的。先继续往下面看。
  ServiceLoader实现了Iterable接口的方法后,具有了迭代的能力,在这个iterator方法被调用时,首先会在ServiceLoader的Provider缓存中进行查找,如果缓存中没有命中那么则在LazyIterator中进行查找。publicIteratorSiterator(){returnnewIteratorS(){IteratorMap。EntryString,SknownProvidersproviders。entrySet()。iterator();publicbooleanhasNext(){if(knownProviders。hasNext())returnlookupIterator。hasNext();调用LazyIterator}publicSnext(){if(knownProviders。hasNext())returnknownProviders。next()。getValue();returnlookupIterator。next();调用LazyIterator}publicvoidremove(){thrownewUnsupportedOperationException();}};}
  在调用LazyIterator时,具体实现如下:publicbooleanhasNext(){if(accnull){returnhasNextService();}else{PrivilegedActionBooleanactionnewPrivilegedActionBoolean(){publicBooleanrun(){returnhasNextService();}};returnAccessController。doPrivileged(action,acc);}}privatebooleanhasNextService(){if(nextName!null){}if(configsnull){try{通过PREFIX(METAINFservices)和类名获取对应的配置文件,得到具体的实现类StringfullNamePREFIXservice。getName();if(loadernull)configsClassLoader。getSystemResources(fullName);elseconfigsloader。getResources(fullName);}catch(IOExceptionx){fail(service,Errorlocatingconfigurationfiles,x);}}while((pendingnull)!pending。hasNext()){if(!configs。hasMoreElements()){}pendingparse(service,configs。nextElement());}nextNamepending。next();}publicSnext(){if(accnull){returnnextService();}else{PrivilegedActionSactionnewPrivilegedActionS(){publicSrun(){returnnextService();}};returnAccessController。doPrivileged(action,acc);}}privateSnextService(){if(!hasNextService())thrownewNoSuchElementException();StringcnnextNnextNC?try{cClass。forName(cn,false,loader);}catch(ClassNotFoundExceptionx){fail(service,Providercnnotfound);}if(!service。isAssignableFrom(c)){fail(service,Providercnnotasubtype);}try{Spservice。cast(c。newInstance());providers。put(cn,p);}catch(Throwablex){fail(service,Providercncouldnotbeinstantiated,x);}thrownewError();Thiscannothappen}
  4。总结
  其实不难发现,SPI机制的具体实现本质上还是通过反射完成的。即:我们按照规定将要暴露对外使用的具体实现类在METAINFservices文件下声明。
  其实SPI机制在很多框架中都有应用:Spring框架的基本原理也是类似的反射。还有dubbo框架提供同样的SPI扩展机制。
  通过SPI机制能够大大地提高接口设计的灵活性,但是SPI机制也存在一些缺点,比如:遍历加载所有的实现类,这样效率还是相对较低的;当多个ServiceLoader同时load时,会有并发问题。
  写在最后
  FreemenApp是一款专注于IT程序员求职招聘的一个求职平台,旨在帮助IT技术工作者能更好更快入职及努力协调IT技术者工作和生活的关系,让工作更自由!
  本文转载自江璇Up
投诉 评论

杜兰特单赛季最高得分2593分,同样是超巨,乔科詹最高多少分强队赢不了,弱队赢得磕磕绊绊,篮网虽然高居东部第一,但新赛季的表现真的让球迷揪心,更看不到夺冠的希望。欧文暂且不提,他现在是半退役状态,别指望欧文复出救场,哈登的表现完全不是巨……女人要想皮肤好,常吃7种含花青素食物,美容抗衰老,价格还实惠女性对自己的容颜总是非常关注,就比如我,总希望自己的皮肤可以一直紧致嫩滑,每天照照镜子,眼角有了一个小皱纹,或是脸上长了一个小包包,就特别紧张,常常买些面膜定期敷一敷,有的朋友……现在才知道,海参不能随便吃,尤其这8类人,为了健康,别再忽视说到海参,大家想必都非常的熟悉,大多数人都吃过海参,海参是一种营养价值极高的海鲜产品,随着现在对于海参的普及以及认知,已经逐渐成为老百姓餐桌上的食材和待客的常用食材了。说……张琳艳闪耀欧洲,能否当选世界足球小姐?候选名单已公布!中国女足目前正在广州进行集训,此次集训的34人名单中,有八名海外留洋球员在列,其中最受球迷关注的小球星张琳艳也是不出所料地入选了。海外球员在海外有比赛任务的可以不回来参加集训,……岳阳税务税力量为重大项目建设蓄力赋能红网时刻岳阳2月2日讯(通讯员陈真黎牧)重大项目是加快区域发展的强引擎。日前,随着城陵矶新港区2023年第一批重大项目的集中启动,全市项目建设吹响了冲锋号。岳阳市税务部门靠前部……若鸟为何迅速下树?枪手蓝军一拍即合,1人受伤推倒多米诺在周一才第一次与切尔西接触的情况下,阿森纳只用了不到一天的时间就完成了若日尼奥的转会。那么,枪手、蓝军为什么会迅速完成这笔交易呢?著名记者、转会专家罗马诺透露,阿森纳以1……战网开始退款了,记忆有感网易和暴雪分手后,昨天战网开始退款了,玩了好久也有十多年了,星际二,暗黑三,守望先锋也都原价买过,这些买断的基本随暴雪走了,也不会退一毛钱。魔兽世界是真玩了好久,可以说是……挽狂澜于既倒扶大厦之将倾凯里欧文在篮网最需要的时候挺身而出在过去的一个半赛季里,凯里欧文一直是日常谈话节目头条新闻的中流砥柱,但是原因与篮球无关。从上赛季因纽约市疫苗接种规定而成为兼职球员,到本赛季因对一部带有反犹太动机的电影直……还不会JavaSPI机制?什么是SPI1。背景在面向对象的设计原则中,一般推荐模块之间基于接口编程,通常情况下调用方模块是不会感知到被调用方模块的内部具体实现。一旦代码里面涉及具体实现类,就违反了……赛力斯预计年营收35亿扣非后净亏超40亿销量未达预期雷递网乐天1月31日赛力斯集团股份有限公司(简称赛力斯)日前发布业绩预告,预计2022年营收为33。5亿到35亿元,同比上升100。38和109。36。赛力斯预计2……带你读诗迎风而立迎风而立像杉树一样傲然挺拔用生命支撑起天空给小鸟一份自由给太阳一份潇洒迎风而立让头发飞扬成火把竖起青春的旗帜将一切冰雪融化……人这一生,最好状态是中年作者:洞见苏眉细细静气存于心,志气藏于胸。长久以来,一提到中年,人们往往会联想到危机、油腻。的确,中年人面临着事业、健康、生活的各种关卡,有时让人觉得四面楚歌……
十三五如何遏制重特大事故应届生面试问题巧回答女甲联赛第十一轮杭州队后来居上,再胜深圳追平比分80岁老中医,每周都要吃3道菜,心脏比年轻人还强!越吃越显年缅甸旅游的最佳时间几月份几月份去缅甸旅游好麒麟区二幼启动爱心公益家庭教育指导项目传统专卖店如何转型我们是卖给谁的信息图牢记这要素助力内容快速传播又见造句用又见造句大全最新歇后语太逗了笑得肚子疼鹦鹉鱼亲嘴是什么意思需要制止吗少年长白头发是什么原因全面解析少年白的七大原因运动会感言CCFW2021国芙汉服品牌联合发布秀朱门传统服饰X醉花涧发生类似天津爆炸事故时该如何自救网站广告协议大扁扁豆是转基因的吗天下第一奇书原谅我曾经的疯狂春天宝宝要预防上呼吸道感染的出现种姿势可以减少初夜性爱的痛楚增加快感黑市拳王唐龙(移动网络电话多少)干锅小龙虾的家常做法正宗小龙虾怎么做好吃燕麦与黑豆:农作物如何影响古代中国和英国

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找