青岛网站seo收费,seo外链建设的方法有,网站建设公众号开,wordpress怎么添加价格和购物车原创#xff1a;微信公众号 码农参上#xff0c;欢迎分享#xff0c;转载请保留出处。 八股文背多了#xff0c;相信大家都听说过一个词#xff0c;SPI扩展。
有的面试官就很喜欢问这个问题#xff0c;SpringBoot的自动装配是如何实现的#xff1f;
基本上#xff0c… 原创微信公众号 码农参上欢迎分享转载请保留出处。 八股文背多了相信大家都听说过一个词SPI扩展。
有的面试官就很喜欢问这个问题SpringBoot的自动装配是如何实现的
基本上你一说是基于spring的SPI扩展机制再把spring.factories文件和EnableAutoConfiguration提一下那么这个问题就答的八九不离十了。
就像四五年前我去面试的时候被问到这个问题SPI动态扩展机制这几个词从嘴里一说出来就把面试官唬的一愣一愣的。可能他们也没见过这么能装逼的一句话能简简单单说明白非要拽一个听上去很高大上的词。
话说回来被唬住的可不止是面试官其实还有我自己。至于SPI扩展究竟是个啥是怎么实现的我当时也根本不明白。
不过现在的面试就是这样对线八股文要想唬住面试官就得先唬住自己。
那么我们今天暂且不提spring的SPI扩展先来看看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包
dependencygroupIdcom.cn.hydra/groupIdartifactIdaircondition-standard/artifactIdversion1.0-SNAPSHOT/version
/dependency创建服务类并实现前面定义的接口
public class HangingTypeAirconditionimplements 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 VerticalTypeAirconditionimplements 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包。
dependenciesdependencygroupIdcom.cn.hydra/groupIdartifactIdaircondition-hanging-type/artifactIdversion1.0-SNAPSHOT/version/dependencydependencygroupIdcom.cn.hydra/groupIdartifactIdaircondition-vertical-type/artifactIdversion1.0-SNAPSHOT/version/dependency
/dependencies按照上面的说法虽然每个服务提供者对于接口都有不同的实现但是作为调用者来说它并不需要关心具体的实现类我们要做的是通过接口来调用服务提供者实现的方法。
下面就是关键的服务发现环节我们写一个方法根据型号去调用对应空调的开关方法。
public class AirconditionApp {public static void main(String[] args) {new AirconditionApp().turnOn(VerticalType);}public void turnOn(String type){ServiceLoaderIAircondition 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就可以使用具体功能。
dependencygroupIdorg.slf4j/groupIdartifactIdslf4j-api/artifactIdversion2.0.3/version
/dependency
dependencygroupIdorg.slf4j/groupIdartifactIdslf4j-log4j12/artifactIdversion2.0.3/version
/dependency引入项目后点开它的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提供了一种比较特别的服务发现和调用机制通过接口灵活的将服务调用与服务提供者分离用于提供给第三方实现扩展时还是很方便的。但是也有缺点比方说一旦加载一个接口就会把所有实现类都加载进来可能会加载到不需要的冗余服务。不过站在整体角度上还是给我们提供了一种非常不错的框架扩展、集成的思路。
那么这次的分享就到这里我是Hydra我们下篇再见。 作者简介码农参上一个热爱分享的公众号有趣、深入、直接与你聊聊技术关注领取大量学习资料。 也欢迎添加我好友多多交流。