Spring的SPI机制【spring.factories】
从自动配置开始看一下
组合注解@SpringBootApplication中的注解@EnableAutoConfiguration
@Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}
类AutoConfigurationImportSelector,这里的解析需要知道@Import注解的解析及相关接口,这里略,下面是简略流程
AutoConfigurationImportSelector#selectImports --> #getAutoConfigurationEntry --> # getCandidateConfigurations --> SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class)
接着就是从META-INF/spring.factories文件按规则加载类的流程了
// 加载关心的类型factoryType配置的对应的类, 并实例化这些类 public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) { // 加载器处理... // 根据关心的类型得到所有的name, 一般是全类名列表 List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse); List<T> result = new ArrayList<>(factoryImplementationNames.size()); for (String factoryImplementationName : factoryImplementationNames) { // 无惨构造函数初始化 result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse)); } // 排序 AnnotationAwareOrderComparator.sort(result); // 得到实例列表 return result; } // 加载关心的类型factoryType配置的对应的类, 得到全类名列表 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { // 类加载器 ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } // 获取全类名 String factoryTypeName = factoryType.getName(); // 具体加载逻辑, 返回的是Map<String, List>, 前者即为loadFactoryNames, 后者为相关类 return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); } // 一次性加载加载器能加载的所有 spring.factories 文件并解析 private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { // 缓存, 类加载器作为键, 每个类加载器只加载一次 Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { // "META-INF/spring.factories", 类路径、jar包等内的 Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { // 找到一个文件 URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); // 加载成为 Properties 实例, =隔开的键值对 // 而 spring.factories 的格式为 k=val1,val2,... Properties properties = PropertiesLoaderUtils.loadProperties(resource); // 键, 一般为关心的类型全类名 for (Map.Entry<?, ?> entry : properties.entrySet()) { // 全类名 String factoryTypeName = ((String) entry.getKey()).trim(); // ,隔开的值列表解析 String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { // computeIfAbsent 值为 List, k对应没有则new List, 有则返回并add result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // k:factoryType, v:implementations, 即List去重, 那为什么不使用Set存储呢 result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() // 不可变对象 .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); // 缓存 cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }
- 一次性加载、解析并缓存指定加载器能加载的META-INF/factories文件
- 提供获取所关心的类型的配置名称列表、实例列表的API
META-INF/factories文件示例,为spring-boot-autoconfigure这个jar包下的
键值均是类权限定名称,当根据某Class类型取时,得到的是配置列表(类权限定名称列表或实例列表)
# Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ ....