在springboot的各个依赖包下,咱们常常看到META-INF/spring.factories这个文件。spring.factories文件的内容基本上都是这样的格式:spring
1 # Initializers 2 org.springframework.context.ApplicationContextInitializer=\ 3 org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ 4 org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
咱们看到,这个文件配置了一个key:value格式的数据缓存
1)key是:org.springframework.context.ApplicationContextInitializerspringboot
2)value是:org.springframework.context.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,org.springframework.context.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListenerlua
key声明的是一个接口,value则是这个接口对应的实现类,若是有多个则以","符号分割。url
简单来讲,spring.factories文件包含了一些接口相对应的实现类的配置,咱们经过这些配置就能够知道接口有哪些可选的实现类,并经过反射获取对应的实例对象。就像是简单工厂模式同样,也所以spring将这个文件定义为spring.factories这个名字。spa
下面以ApplicationContextInitializer接口为示例,咱们看看springboot是怎么使用spring.factories的。插件
首先会用classLoader加载类路径下的全部spring.factories的配置内容,loadSpringFactories方法将返回一个key=接口名,value=实现类集合的Map结构设计
1 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { 2 // 先试着取缓存 3 MultiValueMap<String, String> result = cache.get(classLoader); 4 if (result != null) { 5 return result; 6 } 7 8 try { 9 // 获取全部spring.factories的URL 10 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); 11 result = new LinkedMultiValueMap<>(); 12 // 遍历URL 13 while (urls.hasMoreElements()) { 14 URL url = urls.nextElement(); 15 UrlResource resource = new UrlResource(url); 16 // 加载每一个URL中的properties配置 17 Properties properties = PropertiesLoaderUtils.loadProperties(resource); 18 // 遍历每一个配置 19 for (Map.Entry<?, ?> entry : properties.entrySet()) { 20 String factoryClassName = ((String) entry.getKey()).trim(); 21 // 将实现类的配置按照","符号分割开 22 for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { 23 // 逐个添加到接口对应的集合当中 24 result.add(factoryClassName, factoryName.trim()); 25 } 26 } 27 } 28 // 加入缓存 29 cache.put(classLoader, result); 30 return result; 31 } catch (IOException ex) { 32 // ... 33 } 34 }
有了以上这个Map结构,就能够轻松拿到对应接口的实现类集合了,如:code
1 public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { 2 String factoryClassName = factoryClass.getName(); 3 return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); 4 }
这里的factoryClass是接口,经过getName()方法获取全限定名,而后根据该全限定名从Map结构中get出对应的实现类全限定名的集合。对象
到这里咱们获得了一个实现类的集合,要获取实现类具体的实例对象只须要经过反射获得实例对象便可,如:
1 private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, 2 ClassLoader classLoader, Object[] args, Set<String> names) { 3 List<T> instances = new ArrayList<>(names.size()); 4 // 遍历实例对象的全限定名 5 for (String name : names) { 6 try { 7 // 加载该类 8 Class<?> instanceClass = ClassUtils.forName(name, classLoader); 9 // 断言是否为该接口的实现类 10 Assert.isAssignable(type, instanceClass); 11 // 获取构造方法 12 Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); 13 // 实例化该类 14 T instance = (T) BeanUtils.instantiateClass(constructor, args); 15 // 添加到结果集当中 16 instances.add(instance); 17 } 18 catch (Throwable ex) { 19 throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); 20 } 21 } 22 return instances; 23 }
spring.factories就像是工厂同样配置了大量的接口对应的实现类,咱们经过这些配置 + 反射处理就能够拿到相应的实现类。这种相似于插件式的设计方式,只要引入对应的jar包,那么对应的spring.factories就会被扫描到,对应的实现类也就会被实例化,若是不须要的时候,直接把jar包移除便可。