语录平时听API比较多?什么是SPI?别着急,让我们看一下面向接口编程的调用关系,了解API和SPI的异同关系。SPI的理解首先来一句普通话的介绍:SPI的全称是(ServiceProviderInterface),是JDK内置的一种服务提供者发现机制。(听完惊呆了)好吧,我们用图来理解吧。简单来说就是分为caller、interface、server。接口是一种协议,一种契约,可以由调用者定义,也可以由服务器定义,即接口可以位于调用者的包中,也可以位于服务器的包中。1、当接口的定义和实现都在服务端,只有接口暴露给调用者时,我们称之为API;2.当接口的定义在调用者(在服务器端实现)时,我们也称它为Aname——SPI。应该更容易理解吧?SPI使用场景SPI在框架中其实有很多广泛的应用,这里举几个例子:1.Mysql驱动选择driverManager根据配置决定要使用的驱动;2、dubbo框架中的扩展机制(dubbo官网链接)看了上面的介绍以及SPI在框架中的应用,我想SPI在读者的脑海中已经形成了一个雏形,话不多说!给我看代码。说了这么多,还是写个简单的例子来看看效果,验证一下SPI吧。1.首先定义一个接口,ninja服务接口publicinterfaceNinjaService{voidperformTask();}2.接下来写两个实现类,ForbearanceServiceImpl(上容),ShinobuServiceImpl(下容)publicclassForbearanceServiceImplimplementsNinjaService{@OverridepublicvoidperformTask(){System.out.println("ForbearanceServiceImpl实现NinjaService");}}公共类ShinobuServiceImpl实现NinjaService{@OverridepublicvoidperformTask(){System.out。println("下一级正在执行D级任务");}}3.接下来我们在main/resources/下创建一个META-INF/services目录,并在services目录下创建一个com.scott.java.task.spi。NinjaService(忍者服务类的完全限定名称)文件。4.创建Client场景类调用看结果。完美调用了两个实现类的performTask()方法。5.最后贴上目录结构,附上一波代码示例的地址,spi中,git链接;SPI源码简单分析1.先看核心类ServiceLoader的定义和属性//继承Iterable类时,使用publicfinalclassServiceLoaderiimplementsIterable{//这就是为什么需要在META-INF/services/目录下创建服务类文件的原因privatestaticfinalStringPREFIX="META-INF/services/";//加载服务privatefinalClassservice;//类加载器privatefinalClassLoaderloader;//访问控制类privatefinalAccessControlContextacc;//实现类的缓存是按照初始化的顺序来定义的,也就是/services/文件中定义的加载顺序privateLinkedHashMapproviders=newLinkedHashMap<>();//延迟加载迭代器privateLazyIteratorlookupIterator;2.然后从客户端启动,然后调试到ServiceLoaderninjaServices=ServiceLoader.load(NinjaService.class);publicstaticServiceLoaderload(Classservice){//获取当前类加载器AppClassLoaderClassLoadercl=Thread.currentThread().getContextClassLoader();返回ServiceLoader.load(服务,cl);}publicstaticServiceLoaderload(Classservice,ClassLoaderloader){returnnewServiceLoader<>(service,loader);}省略,因为这里只是NinjaService的初始化,没有什么难理解的。3、我们在看具体的调用过程,这里使用client对应的class文件,因为加入for(foreach)是java中的一个语法糖。其实编译后的内容是这样的publicstaticvoidmain(String[]args){ServiceLoaderninjaServices=ServiceLoader.load(NinjaService.class);//这里其实是在加对脱糖代码感兴趣的可以去了解下javaIteratorvar2=ninjaServices.iterator()的语法糖;while(var2.hasNext()){NinjaServiceitem=(NinjaService)var2.next();item.performTask();}}4.随着断点的继续,我们进入var2.hasNext()方法publicbooleanhasNext(){//knownProviders还没有加载provider,转到下面的分支if(knownProviders.hasNext())returntrue;返回lookupIterator.hasNext();这里引入了ServiceLoaderonlookupIterator的属性,它实际上是ServiceLoader中Iterator的一个内部类,然后调用内部类Iterator的hasNext()方法。publicbooleanhasNext(){//acc=(System.getSecurityManager()!=null)?AccessController.getContext():空;//ServiceLoader初始化没有设置securityManager,所以acc为null,进入hasNextService()if(acc==null){returnhasNextService();}else{PrivilegedActionaction=newPrivilegedAction(){publicBooleanrun(){returnhasNextService();}};返回AccessController.doPrivileged(action,acc);}}5.hasNextService()分析privatebooleanhasNextService(){if(nextName!=null){returntrue;}if(configs==null){try{//META-INF/services下的文件被加载到这里即一个包含两个实现类完全限定名的文件StringfullName=PREFIX+service.getName();if(loader==null)configs=ClassLoader.getSystemResources(全名);else//因为加载器不为空AppClassLoaderconfigs=loader.getResources(fullName);}catch(IOExceptionx){fail(service,"错误定位配置文件",x);}}while((pending==null)||!pending.hasNext()){if(!configs.hasMoreElements()){returnfalse;}//这里是上面加载的文件中的两个实现类的文件pending=parse(service,configs.nextElement());}nextName=pending.next();返回真;}6.继续看parse方法,这里最后返回的是一个包含两个全限定类名的Iterator,其实就是加载services/下的文件内容privateIteratorparse(Class>service,URLu)throwsServiceConfigurationError{InputStreamin=null;BufferedReaderr=null;ArrayList名称=newArrayList<>();尝试{in=u.openStream();r=newBufferedReader(newInputStreamReader(in,"utf-8"));国际电联=1;while((lc=parseLine(service,u,r,lc,names))>=0);}catch(IOExceptionx){fail(service,"读取配置文件时出错",x);}finally{try{if(r!=null)r.close();如果(在!=null)in.close();}catch(IOExceptiony){fail(service,"Errorclosingconfigurationfile",y);}}returnnames.iterator();}顺便说一下parseLine(service,u,r,lc,names),检查class名称是否符合规范,如果符合,就加入Iterator,这里是var2.hasNext()被执行,结果是加载了services下的文件内容ln=r.readLine();如果(ln==null){返回-1;}intci=ln.indexOf('#');如果(ci>=0)ln=ln.substring(0,ci);ln=ln.trim();intn=ln.length();if(n!=0){if((ln.indexOf('')>=0)||(ln.indexOf('\t')>=0))fail(service,u,lc,"非法配置-文件语法");intcp=ln.codePointAt(0);if(!Character.isJavaIdentifierStart(cp))fail(service,u,lc,"非法提供者类名:"+ln);对于(inti=Character.charCount(cp);ic=null;try{//这里nextName上面已经赋值了,所以反射创建一个实例c=Class.forName(cn,false,loader);}catch(ClassNotFoundExceptionx){fail(service,"Provider"+cn+"notfound");}//类型判断if(!service.isAssignableFrom(c)){fail(service,"Provider"+cn+"不是子类型");}try{//转换类型Sp=service.cast(c.newInstance());//将class添加到ServiceLoader的providers属性中并返回providers.put(cn,p);返回p;}catch(Throwablex){fail(service,"Provider"+cn+"无法实例化",x);抛出新的错误();//这不可能发生}8.这里的子类实现类返回返回,分析结束。总结:1.了解什么是SPI;2、SPI和API的简单区别和联系;3.学习如何使用SPI扩展服务;4、分析ServiceLoader的源码加载过程,这里简单的说,META-INF/services定义了要实现的接口(文件名)和实现类(文件内容)。ServiceLoader在加载时并没有实例化实现类,而是在iterator遍历时使用反射创建了一个实例。如果觉得写的还可以,点个赞,关注一下,以后继续写出更好的文章~XD参考资料:1.http://cr.openjdk.java.net/~m...2.https://www.cnblogs.com/happy...3.http://dubbo.apache.org/zh-cn...4.https://www.cnblogs.com/google...5.https://zhuanlan.zhihu.com/p/...