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

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

  1、简介
  SPI的全称是 Service Provider Interface ,翻译过来就是服务提供者的接口,它所实现的其实是一种服务的发现机制。
  这么说起来可能还是有点不好理解,我举个例子来类比一下。
  在spring项目中,写service层代码前,会约定俗成的会添加一个接口层。然后通过spring中的依赖注入,可以借助 @Autowired 等方式注入这个接口的实现类的实例对象,之后对于service的调用一般也基于接口操作。
  简单形容就是这样的:
  如图所示,接口、实现类都是由服务提供方提供,我们可以把controller看作服务调用者,调用方只管调用接口就可以了。
  虽然也有声音认为,大部分情况下service只有一个实现类,接口层显得有些多余。但是在《Head First Design Patterns》这本书中,大佬们还是建议过:
  Program to an interface, not an implementation.
  没错,就是常说的 要面向接口编程 。至于好处,也不外乎是降低耦合度、方便日后扩展、提高了代码的灵活性和可维护性等等。
  在上面这个例子里,这个接口层和其中的方法我们可以称之为 API ,而我们要讨论的 SPI 和它相比,有类似也有差异,还是先看图:
  简单来说,就是服务的调用方定义一个接口规范,可以由不同的服务提供者实现。并且,调用方能够通过某种机制来发现服务提供方,并通过接口调用它的能力。
  通过对比,我们可以看出它们虽然都有着 接口 这一层面,但还是有很大的不同:
  API中的接口是服务提供者给服务调用者的一个功能列表,而SPI中更多强调的是,服务调用者对服务实现的一种约束,服务提供者根据这种约束实现的服务,可以被服务调用者发现。
  说白了,Java中的SPI实现的就是,你按我的接口规范实现服务,我就能通过某种机制为这个接口寻找到这个服务。
  这么说起来可能还有些抽象,下面我们举一个例子,类比具体描述一下这个过程。 2、定义接口
  说起智能家居系统,大家现在都比较熟悉了,只要是相同品牌下的产品,连上wifi就能够通过手机app控制了,非常方便。
  虽然产品不断更新换代,型号更新层出不穷,但是同种家电在app上操作起来,功能一般都是一样的。就拿空调来说,我们在app上操作起来一般也就三个主要功能: 开关 , 选模式 , 调节温度 。
  假设我现在在客厅、卧室、书房安装了3款不同型号的空调,并把它们都接入到了我app中,那么之后的操作都是相同的几个按键,简单粗暴。
  思考一下,无论是开关还是调温,都是通过app去调用设备的接口罢了,那么如果不同型号的空调各写各的接口,后端app在开发的时候光对接接口都麻烦的要死。
  解决方法也很简单,我先定义一套接口规范,不管你以后什么型号的空调,都按我的规范来实现接口。以后只要我能发现你的设备,那么都可以按相同的方法来调用接口。
  那么下面就先来定义这么一套接口规范,如果你以后想要接入智能家居系统,那么就要遵循这个规范来开发接口。
  新建一个项目作为标准,就叫 aircondition-standard 好了,然后创建一个接口。除了3个操作以外,我们再添加一个获取空调型号的方法。public interface IAircondition {     // 获取型号     String getType();          // 开关     void turnOnOff();      // 调节温度     void adjustTemperature(int temperature);      // 模式变更     void changeModel(int modelId); }
  这个接口后面要给服务的实现方来使用,用maven把它打成jar包: mvn clean install
  之后服务提供者在项目中就可以引入这个jar包了,有了这套规范,就保证了产品后期不管怎么更新换代,都能接入到系统来。 3、服务实现
  制定并发布完规则后, 挂式空调 作为第一个服务提供者就来了,新建一个项目 aircondition-hanging-type ,并引入刚才打好的jar包:      com.cn.hydra     aircondition-standard     1.0-SNAPSHOT 
  创建服务类,并实现前面定义的接口: public class HangingTypeAircondition         implements IAircondition{     public String getType() {         return "HangingType";     }          public void turnOnOff() {         System.out.println("挂式空调开关");     }      public void adjustTemperature(int i) {         System.out.println("挂式空调调节温度");     }      public void changeModel(int i) {         System.out.println("挂式空调更换模式");     } }
  在项目的 resources 的目录下,创建 META-INF/services 目录,然后以前面定义的接口名 com.cn.hydra.IAircondition 创建文件,并在文件中写入实现类的全限定名。 com.cn.hydra.HangingTypeAircondition
  整个项目结构非常简单:
  这样,一个服务方的简单实现就搞定了,用maven打成jar包,之后就可以提供给调用方使用了。
  同理,我们可以再创建一个 立式空调 的项目 aircondition-vertical-type ,也只创建一个服务类:public class VerticalTypeAircondition         implements IAircondition{     public String getType() {         return "VerticalType";     }          public void turnOnOff() {         System.out.println("立式空调开关");     }      public void adjustTemperature(int i) {         System.out.println("立式空调调节温度");     }      public void changeModel(int i) {         System.out.println("立式空调更换模式");     } }
  还是按上面的命名规则,创建一个配置文件: com.cn.hydra.VerticalTypeAircondition
  同样,打成jar包就完事了,至于服务调用者如何去发现和调用这两个服务,下面详细再说。 4、服务发现
  现在两个服务提供方都实现了接口,下面关键的一步就是服务发现,这一步java中的spi发现机制已经帮我们实现好了。
  创建一个新项目 aircondition-app ,引入上面打好的两个jar包。              com.cn.hydra         aircondition-hanging-type         1.0-SNAPSHOT                    com.cn.hydra         aircondition-vertical-type         1.0-SNAPSHOT      
  按照上面的说法,虽然每个服务提供者对于接口都有不同的实现,但是作为调用者来说,它并不需要关心具体的实现类,我们要做的是通过接口来调用服务提供者实现的方法。
  下面,就是关键的服务发现环节,我们写一个方法,根据型号去调用对应空调的开关方法。 public class AirconditionApp {     public static void main(String[] args) {         new AirconditionApp().turnOn("VerticalType");     }      public void turnOn(String type){         ServiceLoader load = ServiceLoader                 .load(IAircondition.class);          for (IAircondition iAircondition : 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中引入slf4j-log4j12 ,就可以使用具体功能。     org.slf4j     slf4j-api     2.0.3       org.slf4j     slf4j-log4j12     2.0.3 
  引入项目后,点开它的jar包看一下具体结构:
  有没有发现一个彩蛋,先说为什么我们pom中引入的明明是 slf4j-log4j12 ,实际上引入的是 slf4j-reload4j ?翻一下官网的文档:
  大意就是在2015年和2022年, log4j1.x 就已经宣布end of life 终止了,原因也不难猜,估计是因为频繁爆出的漏洞。在那之后,slf4j-log4j 在构建阶段就会自动重定向到slf4j-reload4j 了,并且官方也强烈建议使用slf4j-reload4j 作为替代。
  再回头看一下jar包的 META-INF.services 里面,通过spi注入了Reload4jServiceProvider 这个实现类,它实现了SLF4JServiceProvider 这一接口,在它的初始化方法initialize() 中,会完成初始化等工作,后续可以继续获取到LoggerFactory 和Logger 等具体日志对象。7、总结
  Java中的SPI提供了一种比较特别的服务发现和调用机制,通过接口灵活的将服务调用与服务提供者分离,用于提供给第三方实现扩展时还是很方便的。但是也有缺点,比方说一旦加载一个接口,就会把所有实现类都加载进来,可能会加载到不需要的冗余服务。不过站在整体角度上,还是给我们提供了一种非常不错的框架扩展、集成的思路。

上甘岭战役的一名女兵,79年在美国被人认出当年在朝鲜您救过我1950年,毕业后在家中赋闲的刘禄曾看到了一条让自己喜出望外的好消息中央下令,面向全国招募英语能力突出的人!在大学期间,学得一口流利英语的刘禄曾想到自己的日夜苦练终于有了用武之地,昆仑山为何常年有重兵守卫,只因多年前的一大发现,至今还是禁区中国幅员辽阔,群山林立,但要说最具代表性的山脉,非昆仑山莫属。家里孩子很爱听科学书籍,也很爱看这类的电视,他总是会向家人提出一些稀奇古怪的问题。其中有许多问题,至今连科学家都不能解中国历代民族问题盘点蒙古(五)窝阔台攻宋灭金之后,蒙古大军主力北还,仅留塔察儿(成吉思汗幼弟铁木格斡赤斤之孙)速不台及张柔(金国降将,汉人)等监视宋军动向。南宋此时宋理宗在位,想趁蒙古北撤之机,收回河南地区。主比鞭子锁链烙铁恐怖多了,为何轧棉机成为黑奴最痛恨的发明?十六世纪,欧洲殖民者就展开了大规模的黑奴贸易活动,大批大批的黑奴被贩卖到美洲。在之后两百多年的时间里,美洲黑人数量激增至1000多万。随着大批黑奴如牲畜一样被拉到美洲,美国原本最匮作为封建王朝末期的女性掌权人,从两方面看,慈禧与闵妃有何不同参政身份虽然慈禧太后和闵妃在参政背景和局限性上都具有一定的相似性,但两者的参政方式并不相同,慈禧垂帘听政属直接参政,而闵妃需要借高宗的支持来发布政令属间接参政。具体地讲,慈禧太后执王维君自故乡来王维年轻的时候就离开了家乡蒲州(今山西省永济市),到长安去求取功名。他21岁中了状元,做了大唐的太乐丞。但很快,因为署中的伶人私自舞黄狮子而受到牵连,被谪为济州参军司法,在任上蹉跎大同名人刘撝一门四代八进士天上一轮才捧出,人间万姓仰头看。中国古代社会,十年寒窗苦读,为的就是金榜题名,而状元及第那绝对是光耀门庭却难与上青天的事,据统计自唐高祖武德五年(622年)的第一位科举状元孙伏伽开北京移动与北京急救中心合作建设紧急医疗救援5G急救系统假如身边有人突发急病,你知道应该如何正确救治吗?生死关头,普通人可能会手忙脚乱不知如何下手,而宝贵的急救时间也就在这时一分一秒地流逝了。最近来自北京急救中心各大医院和通信技术专家们白鹭振翅!厦门新体育中心建设进入冲刺阶段厦门新体育中心的白鹭体育场的屋面工程正在进行膜结构施工。站在环岛东路,朝东偏北方向,将目光越过宽阔的海面,能看到一个马鞍形的体育场作为国内唯一能够在场内看海的体育场,厦门新体育中心月子中心成风口,但准入门槛低,价格与服务难匹配动辄数万元,服务标准待规范月子中心成风口,但准入门槛低,价格与服务难匹配在长沙一家培训机构,学员正在给婴幼儿进行护理。受访者供图作为母婴产后护理机构,月子中心提供包括婴儿护理产妇照金山新城核心区新控规定位惠城新副中心,惠州中央创新区近期,惠州市自然资源局组织编制的惠州市金山新城核心区控制性详细规划(道路绿地水系部分)(下称新控规)正式通过市政府批准实施,明确以惠城南站为中心的金山新城核心区将成为惠城未来南拓的
张姓艺人,代言赌博平台,他将承担什么法律后果?不用承担法律后果。因为没有犯法。张姓艺人代言的是国外的赌博平台,并不在国内上线,所以我们不能因为他是中国人就反对其代言行为,就像周星驰先生和刘德华先生早年间也拍过赌博相关的电影,我眯眯眼事件为什么突然热起来?是某资本在玩金蝉脱壳吗?说白了是汉奸太多,为了获奖,为了钱,为了名声,国人算什么,只要自己过的好,哪怕你们水深火热!希望国家严厉打击这些投机分子!故意改变眯眯眼原本的争论点,这事很蹊跷!是制造对立还是玩金明明智能电视都普及了,为什么还有很多家庭开通有线电视呢?家里只有不会操作的老人时,有线电视好处就来了,老式遥控和界面更符合他们的使用习惯,不用费劲吧啦地再教也学不会。更重要的是不卡顿没延迟,节目消费也比较透明,不会不知不觉就乱扣钱。有线吉利星瑞和思域哪个值得买?感谢邀请回答。吉利星瑞和思域哪个值得买?这两辆车,一辆是国产新锐轿车,一辆是合资品牌很有名气的轿车,一起来了解一下,进来玩车险吧!两车在售价上也几乎相当,至于说哪个值得买?我认为还本人女,想买一辆家用车,只想在科沃兹和起亚K3之间选,选哪个好?科沃兹起亚K3两车级别相同但价格悬殊很大,而且主动安全配置差距明显,女性用车建议选择安全系数更高的科沃兹。起亚K3优缺点指导价9。6813。28万有一定幅度优惠,相比车辆本身的品质想要开店,没技术,学什么来开店比较好?(一)蛋糕甜点创业,45天左右的时间学习蛋糕甜点制作,主要学习蛋糕制作如海绵蛋糕戚风蛋糕天使蛋糕重油蛋糕奶酪蛋糕慕斯蛋糕学习甜品制作,如拿破仑马卡龙泡芙柠檬挞巧克力歌剧院等学习面包你喝过最难喝的饮料是什么?恒大矿泉水l假饮料农夫果园30混合果蔬。农夫果园推出这种混合型果汁饮料看起来还是很诱人的,也有多种口味选择,满足了不同用户的需求,但是入口感觉的确难接受。你喝过最难喝的饮料是什么?黄金和比特币,你会收藏哪个?比特币。因为第一,比特币便于携带。它无论是热钱包,还是冷钱包都很便于携带。黄金就不同了,太多了,你搬着都费劲啊。第二,便于适用。比特币支付你直接扫码就可以了。黄金就麻烦多了,难道你警方是通过什么渠道得知信息的?为什么扫黄的准确率那么高?这事我还真遇见过。2018年,我和我对象去外地拍婚纱照,所以提前在网上预定了一家快捷酒店,拍完婚纱照结束后,就回到酒店休息了。当天半夜正睡得很沉时,被房间外的咋呼声吵醒,隐约听见外腰间盘突出物会不会越来越大?这需要看患者是否采取有效的治疗!腰突患者嘴中的突出物指的是椎间盘里的髓核,它本身对于椎间盘没有太大影响,但其突出后却会对周围神经组织产生压迫刺激,导致其发炎肿胀,出现各种症状。而腰肝功能化验单主要看哪些项目?为什么?随着影像学技术的推陈出新,很多肝脏胆囊胰腺方面的疾病,可以更快速更准确便得到结果。而肝功能检查,从抽血到发报告,需要时间较长,就我自感觉,它已经沦为判断病情轻重的辅助指标。肝功能化