概述
SPI,Service Provider Interface,一种服务发现机制,指一些提供给你继承、扩展,完成自定义功能的类、接口或方法。
在SPI机制中,服务提供者为某个接口实现具体的类,而在运行时通过SPI机制,查找到对应的实现类,并将其加载进行使用。
JDK 6(参考java.util.ServiceLoader类的注释Since字段)引进的一个特性,可实现动态加载具体实现类的机制,通过它可以具体实现代码的解耦,也可实现类似于IoC的效果。
SPI的实现方式:提供实现的实现类打包成jar文件,jar文件里面必须有个META-INF/services目录,其下有一个文本文件,文件名即为SPI接口的全限定名(完整路径名),文件的内容是该jar包中提供的SPI接口的实现类名。文件编码是UTF-8。
作用有两个:
- 把标准定义和接口实现分离,在模块化开发中很好地实现解耦
- 实现功能的扩展,更好地满足定制化需求
SPI的不足之处:不能根据需求去加载扩展实现,每次都会加载扩展接口的所有实现类并进行实例化,实例化会造成性能开销,并且加载一些不需要用到的实现类,会导致内存资源的浪费。
API和SPI
API,提供给他人使用的具备某项功能的类、接口或方法。
SPI用于一些通用的标准中,为标准的实现产商提供扩展点。标准在上层提供API,API内部使用SPI,当API被客户使用时,会动态得从当前运行的classpath中寻找该SPI的实现,然后使用该SPI的实现来完成API的功能。
实例
JDBC
java.sql.Driver数据库驱动接口,JDK中只提供接口的定义,具体的实现类由各个数据库厂商提供的驱动包来完成,程序在运行的时候会根据当前导入的驱动包来完成对应数据库的连接。
如上图,java.sql.Driver文件内容:com.mysql.cj.jdbc.Driver。
同理也能在postgresql-42.7.2.jar下找到java.sql.Driver文件,内容为org.postgresql.Driver,截图省略。
com.mysql.cj.jdbc.Driver源码很简单:
publicclassDriverextendsNonRegisteringDriverimplementsjava.sql.Driver{// Register ourselves with the DriverManager.static{try{DriverManager.registerDriver(newDriver());}catch(SQLExceptionE){thrownewRuntimeException("Can't register driver!");}}publicDriver()throwsSQLException{}}核心方法就一行:DriverManager.registerDriver(new Driver());,在PG源码org.postgresql.Driver里也能找到DriverManager.registerDriver()。
基于JDK 22源码,在DriverManager类里搜索文件名java.sql.Driver,找到方法ensureDriversInitialized:
/* * Load the initial JDBC drivers by checking the System property jdbc.drivers and then use the ServiceLoader mechanism */privatestaticvoidensureDriversInitialized(){// 检查驱动是否已经初始化成功if(driversInitialized){return;}synchronized(lockForInitDrivers){// 优先从环境变量jdbc.drivers获取驱动Stringdrivers;try{drivers=AccessController.doPrivileged(newPrivilegedAction<String>(){publicStringrun(){returnSystem.getProperty("jdbc.drivers");}});}catch(Exceptionex){drivers=null;}// If the driver is packaged as a Service Provider, load it.// Get all the drivers through the classloader exposed as a java.sql.Driver.class service.// ServiceLoader.load() replaces the sun.misc.Providers()AccessController.doPrivileged(newPrivilegedAction<Void>(){publicVoidrun(){// ServiceLoaderServiceLoader<Driver>loadedDrivers=ServiceLoader.load(Driver.class);Iterator<Driver>driversIterator=loadedDrivers.iterator();/* 加载驱动然后初始化,存在一种情况,有java.sql.Driver文件,但是文件内容为空, * 或找不到文件内容指向的Driver类,用Throwable来catch java.util.ServiceConfigurationError */try{// TODO: loadedDrivers并没有赋值到drivers?while(driversIterator.hasNext()){driversIterator.next();}}catch(Throwablet){// Do nothing}returnnull;}});if(drivers!=null&&!drivers.isEmpty()){String[]driversList=drivers.split(":");for(StringaDriver:driversList){try{Class.forName(aDriver,true,ClassLoader.getSystemClassLoader());}catch(Exceptionex){// just logging}}}// 设置驱动初始化成功标志driversInitialized=true;}}ensureDriversInitialized()方法的调用方有getDriver()和getConnection()两个,即获取驱动和获取连接。
Spring
在Spring源码里也有SPI的影子,如spring.factories文件和SpringFactoriesLoader,两者一起构成Spring Boot Starter实现的基础。
以3.2.4版本来讲解,位于spring-boot-actuator-autoconfigure-3.2.4.jar下的spring.factories文件片段:
# Failure Analyzersorg.springframework.boot.diagnostics.FailureAnalyzer=\ org.springframework.boot.actuate.autoconfigure.metrics.ValidationFailureAnalyzer,\ org.springframework.boot.actuate.autoconfigure.health.NoSuchHealthContributorFailureAnalyzer可见,此文件实际上是.properties文件格式,不同类型的配置项用空行隔开,方便阅读,支持#作为注释字符。配置是键值对形式,Key和Value都是代表类的完整包名,Value允许有多个,用,分隔,多行文本以\分隔;Key是接口、注解、或抽象类,Value是Key的实现类。
而SpringFactoriesLoader自Spring 3.2版本开始就已存在,其属性如下:
// spring.factories文件默认检索位置publicstaticfinalStringFACTORIES_RESOURCE_LOCATION="META-INF/spring.factories";// 默认抛出异常privatestaticfinalFailureHandlerTHROWING_FAILURE_HANDLER=FailureHandler.throwing();// ClassLoader级别的缓存,Value也是Map,K为资源目录,V为全路径类staticfinalMap<ClassLoader,Map<String,SpringFactoriesLoader>>cache=newConcurrentReferenceHashMap<>();// 用于加载类@NullableprivatefinalClassLoaderclassLoader;// 用于保存spring.factories文件里的键值对privatefinalMap<String,List<String>>factories;核心方法有两个,forResourceLocation是一个静态方法,用于获取实例:
publicstaticSpringFactoriesLoaderforResourceLocation(StringresourceLocation,@NullableClassLoaderclassLoader){Assert.hasText(resourceLocation,"'resourceLocation' must not be empty");ClassLoaderresourceClassLoader=(classLoader!=null?classLoader:SpringFactoriesLoader.class.getClassLoader());Map<String,SpringFactoriesLoader>loaders=cache.computeIfAbsent(resourceClassLoader,key->newConcurrentReferenceHashMap<>());returnloaders.computeIfAbsent(resourceLocation,key->newSpringFactoriesLoader(classLoader,loadFactoriesResource(resourceClassLoader,resourceLocation)));}参考resourceLocation,可传入指定路径,如果为空,则从META-INF/spring.factories查找。
load方法则用于获取指定类型的实现类集合:
public<T>List<T>load(Class<T>factoryType,@NullableArgumentResolverargumentResolver,@NullableFailureHandlerfailureHandler){Assert.notNull(factoryType,"'factoryType' must not be null");List<String>implementationNames=loadFactoryNames(factoryType);List<T>result=newArrayList<>(implementationNames.size());FailureHandlerfailureHandlerToUse=(failureHandler!=null)?failureHandler:THROWING_FAILURE_HANDLER;for(StringimplementationName:implementationNames){Tfactory=instantiateFactory(implementationName,factoryType,argumentResolver,failureHandlerToUse);if(factory!=null){result.add(factory);}}AnnotationAwareOrderComparator.sort(result);returnresult;}Dubbo
打开dubbo-3.2.14源码,发现META-INF/services目录下有个org.apache.dubbo.common.extension.LoadingStrategy文本文件,内容如下:
org.apache.dubbo.common.extension.DubboInternalLoadingStrategy org.apache.dubbo.common.extension.DubboLoadingStrategy org.apache.dubbo.common.extension.ServicesLoadingStrategyDubboInternalLoadingStrategy用于查找META-INF/dubbo/internal/目录下的文件,DubboLoadingStrategy用于查找META-INF/dubbo/目录下的文件,ServicesLoadingStrategy用于查找META-INF/services/目录下的文件。
META-INF/dubbo/internal/目录下有91个文件,所有文件名都是Dubbo下的全路径名指向的接口类,这些接口都添加有注解@SPI(value = "", scope = FRAMEWORK),文件内容则是多行键值对,采用Key=Value形式,Key是一个简称,Value则是Dubbo下接口(文件名对应的)的实现类。
SPI注解源码如下:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})public@interfaceSPI{Stringvalue()default"";ExtensionScopescope()defaultExtensionScope.APPLICATION;}ExtensionScope是一个枚举类:
- FRAMEWORK:
- APPLICATION:默认值
- MODULE:
- SELF:
ExtensionLoader是个泛型类,入口方法是getExtension:
publicTgetExtension(Stringname,booleanwrap){checkDestroyed();if(StringUtils.isEmpty(name)){thrownewIllegalArgumentException("Extension name == null");}if("true".equals(name)){// 获取默认拓展实现类returngetDefaultExtension();}StringcacheKey=name;if(!wrap){cacheKey+="_origin";}// Holder机制包装实现类+缓存finalHolder<Object>holder=getOrCreateHolder(cacheKey);Objectinstance=holder.get();// DCLif(instance==null){synchronized(holder){instance=holder.get();if(instance==null){// 核心方法:创建实现类instance=createExtension(name,wrap);holder.set(instance);}}}return(T)instance;}接下来看看createExtension方法:
privateTcreateExtension(Stringname,booleanwrap){Class<?>clazz=getExtensionClasses().get(name);if(clazz==null||unacceptableExceptions.contains(name)){throwfindException(name);}try{Tinstance=(T)extensionInstances.get(clazz);if(instance==null){extensionInstances.putIfAbsent(clazz,createExtensionInstance(clazz));instance=(T)extensionInstances.get(clazz);// 实例化前处理instance=postProcessBeforeInitialization(instance,name);// 向实例中注入依赖injectExtension(instance);// 实例化后处理instance=postProcessAfterInitialization(instance,name);}if(wrap){List<Class<?>>wrapperClassesList=newArrayList<>();if(cachedWrapperClasses!=null){wrapperClassesList.addAll(cachedWrapperClasses);wrapperClassesList.sort(WrapperComparator.COMPARATOR);Collections.reverse(wrapperClassesList);}if(CollectionUtils.isNotEmpty(wrapperClassesList)){for(Class<?>wrapperClass:wrapperClassesList){Wrapperwrapper=wrapperClass.getAnnotation(Wrapper.class);booleanmatch=(wrapper==null)||((ArrayUtils.isEmpty(wrapper.matches())||ArrayUtils.contains(wrapper.matches(),name))&&!ArrayUtils.contains(wrapper.mismatches(),name));if(match){// 将当前instance作为参数传给Wrapper的构造方法,并通过反射创建Wrapper实例,向Wrapper实例中注入依赖,最后将Wrapper实例再次赋值给instance变量instance=injectExtension((T)wrapperClass.getConstructor(type).newInstance(instance));instance=postProcessAfterInitialization(instance,name);}}}}// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.initExtension(instance);returninstance;}catch(Throwablet){// 创建实现类失败thrownewIllegalStateException();}}核心方法是getExtensionClasses:
privateMap<String,Class<?>>getExtensionClasses(){// 检查缓存Map<String,Class<?>>classes=cachedClasses.get();// DCLif(classes==null){synchronized(cachedClasses){classes=cachedClasses.get();if(classes==null){try{classes=loadExtensionClasses();}catch(InterruptedExceptione){logger.error();thrownewIllegalStateException();}cachedClasses.set(classes);}}}returnclasses;}继续看loadExtensionClasses:
privateMap<String,Class<?>>loadExtensionClasses()throwsInterruptedException{checkDestroyed();cacheDefaultExtensionName();Map<String,Class<?>>extensionClasses=newHashMap<>();for(LoadingStrategystrategy:strategies){loadDirectory(extensionClasses,strategy,type.getName());// compatible with old ExtensionFactoryif(this.type==ExtensionInjector.class){loadDirectory(extensionClasses,strategy,ExtensionFactory.class.getName());}}returnextensionClasses;}继续看loadDirectory,两个逻辑,一是调用方法loadDirectoryInternal,二是判断Dubbo 2的向下兼容性,核心方法是loadDirectoryInternal,继续调用方法loadResource,用于读取和解析配置文件,并通过反射加载类,这里面可看到对前面提到的文本文件进行逐行解析,判断=分隔符的逻辑,最后调用 loadClass方法用于操作缓存。loadClass方法调用缓存相关方法,如cachedAdaptiveClass、cachedWrapperClasses和cachedNames等。