SpringFactoriesLoader
Spring最为一个最为流行的开源框架,必然就应该有属于它自己的SPI实现。而SpringFactoriesLoader就是它用来实现SPI的法宝。它在Spring Framework中鲜有应用,但是SpringBoot中被广泛的使用,它的自动配置和这息息相关,因此在讲解boot的自动化配置章节的时候,还会提到它。
上面已经对SPI进行了概念介绍,以及对JDK的标注实现:ServiceLoad进行了描述解释。
我们发现很多框架等都有自己对SPI的实现,
比如tomcat的实:WebappServiceLoader它用于比如容器启动时加载所有的ServletContainerInitializer实现类,从而驱动Spring容器的启动。
Spring的实现:SpringFactoriesLoader它用于SpringBoot中的自动化配置起到了关键甚至决定性作用~
注意:SpringFactoriesLoader的不同之处在于,它内部都是key-value的形式,这是和前两种SPI不一样的地方
使用Demo
是骡子是马,拉出来溜溜就可以了。
SpringFactoriesLoader默认加载的路径和文件为:类路径下META-INF/spring.factories
com.fsx.serviceloader.IService=com.fsx.serviceloader.HDFSService,com.fsx.serviceloader.LocalService // 若有非常多个需要换行 可以这么写 // 前面是否顶头没关系(Spring在4.x版本修复了这个bug) com.fsx.serviceloader.IService=\ com.fsx.serviceloader.HDFSService,\ com.fsx.serviceloader.LocalService
我们这么测试一下即可:
public static void main(String[] args) throws IOException { List<IService> services = SpringFactoriesLoader.loadFactories(IService.class, Main.class.getClassLoader()); List<String> list = SpringFactoriesLoader.loadFactoryNames(IService.class, Main.class.getClassLoader()); System.out.println(list); //[com.fsx.serviceloader.HDFSService, com.fsx.serviceloader.LocalService] System.out.println(services); //[com.fsx.serviceloader.HDFSService@794cb805, com.fsx.serviceloader.LocalService@4b5a5ed1] }
完美work。
使用小细节:
- spring.factories内容的key不只能是接口,也可以是抽象类、具体的类。但是有个原则:=后面必须是key的实现类(子类)
- key还可以是注解,比如SpringBoot中的的key:org.springframework.boot.autoconfigure.EnableAutoConfiguration,它就是一个注解
- 文件的格式需要保证正确,否则会返回[](不会报错)
- =右边必须不是抽象类,必须能够实例化。且有空的构造函数~
- loadFactories依赖方法loadFactoryNames。loadFactoryNames方法只拿全类名,loadFactories拿到全类名后会立马实例化
- 此处特别注意:loadFactories实例化完成所有实例后,会调用AnnotationAwareOrderComparator.sort(result)排序,所以它是支持Ordered接口排序的,这个特点特别的重要。
SpringFactoriesLoader的版本小变化:Spring4.x的时候该类是个抽象类,且没有缓存。Spring5.x后它变成了final类,并且加入了Map<ClassLoader, MultiValueMap<String, String>> cache缓存
原理简单剖析
因为Spring的这个配置文件和上面的不一样,它的名字是固定的spring.factories,里面的内容是key-value形式,因此一个文件里可以定义N多个键值对。我认为它比源生JDK的SPI是更加灵活些的~
它主要暴露了两个方法:loadFactories和loadFactoryNames
// @since 3.2 public final class SpringFactoriesLoader { ... public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; ... // 核心方法如下: private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // 读取到资源文件,遍历 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); // 此处使用的是URLResource把这个资源读进来~~~ UrlResource resource = new UrlResource(url); // 可以看到,最终它使用的还是PropertiesLoaderUtils,只能使键值对的形式哦~~~ 当然xml也是被支持的 Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); // 使用逗号,分隔成数组,遍历 名称就出来了~~~ for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } }
原理其实蛮简单的,主要是SPI这种设计思想,对开闭原则来说还是非常的重要的。因为有了Spring的SPI,所以SpringBoot的自动配置也就自然而然了
Spring中的应用举例
若你不是Boot环境,Spring Framwork中自己应用极少。此处找到一个Spring内部使用者:CachedIntrospectionResults:它是一个内建的还是很有用的一个类,主要和缓存JavaBean有关,还有BeanInfoFactory。它有这么一句使用:
/** Stores the BeanInfoFactory instances. */ private static List<BeanInfoFactory> beanInfoFactories = SpringFactoriesLoader.loadFactories( BeanInfoFactory.class, CachedIntrospectionResults.class.getClassLoader()); // 配置文件内容为 org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory // 也就是说`BeanInfoFactory`的实现类默认使用的`ExtendedBeanInfoFactory`
关于JavaBean、内省、BeanInfoFactory、BeanInfo等等基础,各位可以自行学习了解一下~
SPI解决的问题场景描述
在我们设计一套API供别人调用的时候,如果同一个功能的要求特别多,或者同一个接口要面对很复杂的业务场景,这个时候我们该怎么办呢?
- 其一:我们可以规范不同的系统调用,也就是传递一个系统标识;
- 其二:我们在内部编码的时候可以使用不同的条件判断语句进行处理;
- 其三:我们可以写几个策略类来来应对这个复杂的业务逻辑,比如同一个功能的实现,A实现类与B实现类的逻辑一点也不一样,但是目标是一样的,这个时候使用策略类是毋庸置疑的?
从上面三种实现方案来看,高下立判。当然任何事情都不是绝对的,如果你要求速度,第一种方案可谓是最为快速的,但是是最难维护的
上述的问题我们是在API层面进行处理的。
那万一有一天让我们自己设计一套框架,然后让别人直接使用(比如Spring,比如tomcat)?我们该如何处理上述的这个问题呢?答:SPI技术