【springboot】自动加载分析
文章目录
问题
为什么我们在使用springboot的时候,只需要在maven中导入starter就能够使用呢?这里来分析一下
@SpringBootApplication注解
这个注解一般用在springboot程序的主启动类上,这个注解除去元注解,由下面几个注解构成
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@SpringBootConfiguration是由下面两个注解构成
@Configuration
@Indexed
@EnableAutoConfiguration是由下面内容构成
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
@AutoConfigurationPackage内容如下
@Import(AutoConfigurationPackages.Registrar.class)
所以@SpringBootApplication等价于下面内容
// @SpringBootConfiguration
@Configuration
@Indexed// @EnableAutoConfiguration
@Import(AutoConfigurationPackages.Registrar.class)
@Import(AutoConfigurationImportSelector.class)@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
可以发现实际就是导入了两个类,一个AutoConfigurationPackages.Registrar,一个AutoConfigurationImportSelector。
AutoConfigurationPackages.Registrar类
这个类的信息如下
/* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing* configuration.*/static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));}@Overridepublic Set<Object> determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImports(metadata));}}
作用就是获取要扫描的包路径
AutoConfigurationImportSelector类
这个类的内容如下
里面实现了多个接口以Aware结尾的接口,其实就是完成某种资源的设置,Ordered就是用于指定bean加载顺序的,最重要的是DeferredImportSelector,这个接口继承了ImportSelector,ImportSelector就可以完成bean的注入工作
这个接口里面的group类里面就包含了要进行加载的bean信息
回到实现类,也就是AutoConfigurationImportSelector,去到process方法
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,() -> String.format("Only %s implementations are supported, got %s",AutoConfigurationImportSelector.class.getSimpleName(),deferredImportSelector.getClass().getName()));AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);this.autoConfigurationEntries.add(autoConfigurationEntry);for (String importClassName : autoConfigurationEntry.getConfigurations()) {this.entries.putIfAbsent(importClassName, annotationMetadata);}
}
将这个方法分解一下
// 获取自动配置的类AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);// 遍历自动配置的beanfor (String importClassName : autoConfigurationEntry.getConfigurations()) {// 如果存在这个类就进行加入this.entries.putIfAbsent(importClassName, annotationMetadata);}
里面关键的就是获取所有的自动配置类,关键方法就是getAutoConfigurationEntry这个方法,进入这个方法
/* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}* of the importing {@link Configuration @Configuration} class.* @param annotationMetadata the annotation metadata of the configuration class* @return the auto-configurations that should be imported*/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);}
里面的关键语句就是
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
功能就是完成获取所有要进行自动加载的bean,进入到方法
/* Return the auto-configuration class names that should be considered. By default* this method will load candidates using {@link ImportCandidates} with* {@link #getSpringFactoriesLoaderFactoryClass()}. For backward compatible reasons it* will also consider {@link SpringFactoriesLoader} with* {@link #getSpringFactoriesLoaderFactoryClass()}.* @param metadata the source metadata* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation* attributes}* @return a list of candidate configurations*/protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = new ArrayList<>(SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);Assert.notEmpty(configurations,"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "+ "are using a custom packaging, make sure that file is correct.");return configurations;}
这个方法里面的
List<String> configurations = new ArrayList<>(SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
这个方法用于读取类路径下的所有 META-INF/spring.factories文件
下面的这行代码
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
将其拆解为
ImportCandidates ic = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader());ic.forEach(configurations::add);
可以发现就是先去获取所有自动加载bean,然后将其加入到一个集合中,进入到load方法
/* Loads the names of import candidates from the classpath. The names of the import candidates are stored in files named* {@code META-INF/spring/full-qualified-annotation-name.imports} on the classpath.* Every line contains the full qualified name of the candidate class. Comments are* supported using the # character.* @param annotation annotation to load* @param classLoader class loader to use for loading* @return list of names of annotated classes*/public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {Assert.notNull(annotation, "'annotation' must not be null");ClassLoader classLoaderToUse = decideClassloader(classLoader);String location = String.format(LOCATION, annotation.getName());Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);List<String> importCandidates = new ArrayList<>();while (urls.hasMoreElements()) {URL url = urls.nextElement();importCandidates.addAll(readCandidateConfigurations(url));}return new ImportCandidates(importCandidates);}
里面最重要的语句就是
importCandidates.addAll(readCandidateConfigurations(url));
其实就是读取配置文件的信息,然后加入到集合中。读取的位置就是
META-INF/spring/%s.imports
实际上就是去读取
里面一共有144个默认加载项,然后再经过一些过滤操作,就会返回所有的配置类信息
/* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}* of the importing {@link Configuration @Configuration} class.* @param annotationMetadata the annotation metadata of the configuration class* @return the auto-configurations that should be imported*/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);}
后面springboot就会去处理这些这些配置类。
springboot如何加载其他的starter
其实上面已经说了,springboot会去读取类路径下的所有META-INF/spring.factories文件,所以只需要在这个文件中写上自动配置的类即可,以mybatis-plus为例子,如下
总结
springboot在启动的时候会去读取org\\springframework\\boot\\spring-boot-autoconfigure\\2.7.10\\spring-boot-autoconfigure-2.7.10.jar!\\META-INF\\spring\\org.springframework.boot.autoconfigure.AutoConfiguration.imports这个文件中的所有配置。
springboot通过读取META-INF\\spring.factories下的配置文件来支持其他starter的注入