springboot自动装配的源码与流程图

网友投稿 294 2022-12-22

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 configurations = getCandidateConfigurations(annotationMetadata, attributes);

// 去重

configurations = removeDuplicates(configurations);

// 获取需要去除的信息

Set 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 getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

// 重要

List 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 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> loadSpringFactories(ClassLoader classLoader) {

// 查询缓存

Map> result = cache.get(classLoader);

if (result != null) {

return result;

}

result = new HashMap<>();

try {

// 1. 获取类加载器能读取的所有在META-INF目录下的spring.factories文件

Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);

while (urls.hasMoreElementDoFNts()) {

// 遍历路径

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类型的数据,然后存入本地缓存。

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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:带你了解mybatis如何实现读写分离
下一篇:java并发之Lock接口的深入讲解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~