springboot自动装配的源码与流程图
前言
在使用SpringBoot开发项目中,遇到一些 XXX-XXX-starter,例如mybatis-plus-boot-starter,这些包总是能够自动进行配置,
减少了开发人员配置一些项目配置的时间,让开发者拥有更多的时间用于开发的任务上面。下面从源码开始。
正文
SpringBoot版本:2.5.3
- 从@SpringBootApplication进入@EnableAutoConfiguration
- 然后进入AutoConfigurationImportSelector
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {} @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}
进入AutoConfigurationImportSelector,可以发现该类是ImportSelector接口的实现类,然后直接定位至selectImports方法。
到了这里,其实主动装配的套路就是@EnableXXX加@Import的套路。这就是一个大概的认知了。
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
下面进入getAutoConfigurationEntry方法:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // 获取注解信息 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 获取自动配置的信息 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 去重 configurations = removeDuplicates(configurations); // 获取需要去除的信息 Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 检查 checkExcludedClasses(configurations, exclusions); // 去除需要被去除的配置 configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
详细的注释已经写在代码中的了,这里面最重要的是getCandidateConfigurations方法,其次是下面的过滤排除不需要的配置信息。下面进入getCandidateConfigurations方法。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 重要 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; } protected Class<?> getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; } protected ClassLoader getBeanClassLoader() { return this.beanClassLoader; }
这里需要关注的方法是SpringFactoriesLoader.loadFactoryNames,进入该方法,该方法是一个静态方法。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } // 获取类名 String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); } 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 { // 1. 获取类加载器能读取的所有在META-INF目录下的spring.factories文件 Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { // 遍历路径 URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); // 将路径下的文件数据读取为Properties Properties properties = PropertiesLoaderUtils.loadProperties(resource); // 遍历Properties for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements // 用不可修改列表替换 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; }
- loadSpringFactories方法就是加载并读取所传入的类加载器能读取的所有spring.factories文件,并将读取的数据最终转换为Map<String, List>类型的数据,然后存入本地缓存。
- loadFactoryNames方法就是通过传入factoryType(也就是calss.name)来获取数据,并不是获取所有的数据。所以这里会有很多地方会用到。
- @EnableAutoConfiguration是取的文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration,所以一般自定义starter的自动配置文件都是在这个key后面。例如:org.springframework.boot.autoconfigure.EnableAutoConfiguration= a.b.c.d.XXX;
至此,源码就差不多完成了,其实从源码上来看其实也不难。
大概流程图如下:
最后
说了这么多源码,也画了流程图。这里面最重要的就是SpringFactoriesLoader,从这个类名就可以看出来,是专门处理factories文件的,这个类只提供了两个静态方法,而EnableAutoConfiguration只是取了其中的EnableAutoConfiguration下的数据,那么其它的数据呢,不会用到吗?肯定不会的,所以spring在很多地方会用到这个类,后面看spring源码的时候在来看吧,这里先标记一下。
还有就是,SpringFactoriesLoader和JDK的SPI也是差不多的一个思想。下一篇就来看看JDK的SPI
到此这篇关于springboot自动装配的源码与流程图的文章就介绍到这了,更多相关springboot自动装配内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!