说明
SpringBoot的两大核心,自动配置和依赖管理。SpringBoot的项目,往往只是依靠一个入口便能完成整个项目的加载和启动。在此从启动类的注解来查查启动的大概过程,以下代码基于 springboot的 2.7.4 其使用的Spring版本为 5.3.23
@SpringBootApplication
public class Springboot01DemoApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01DemoApplication.class, args);
}
}
@SpringBootApplication整体结构
@SpringBootApplication注解的声明,可以看出是一个组合注解。
@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 {
...
}
@SpringBootApplication注解的注解组成图

一、@SpringBootConfiguration注解
@SpringBootConfiguration的声明
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
- 定义上是Spring的@Configuration注解的另一种形式,可以替代@Configuration注解使用。
- 用来指示一个类,是提供Spring引导程序的类,一个应用应该只包含一个该注解标注的类。大部分Spring引导程序,都从@SpringBootApplication中继承了这个注解
- @indexd 5.0版本开始加入的,标识需要注入的类,提前生成索引文件META-INT/spring.components避免整个包扫描。需要增加spring-context-indexer依赖才生效。
二、@EnableAutoConfiguration注解
该注解标识开启自动配置功能,是SpringBoot最重要的注解,也是实现自动化配置的注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
3.1 @AutoConfigurationPackage 自动配置包注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry,
new PackageImports(metadata).getPackageNames().toArray(new String[0])
);
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
- @AutoConfigurationPackage 的功能是由@import注解实现的,主要功能是,如果没有声明扫描的包时,就注册被AutoConfigurationPackage 修饰的类所在的包。
- @Import的主要功能,就是给容器中导入某个组件类。
- @Import(AutoConfigurationPackages.Registrar.class) 就是AutoConfigurationPackages.Registrar.class这个组件类,导入到容器中。组件的具体实现,就是Registrar类中registerBeanDefinitions方法。
- 也就是说 @AutoConfigurationPackage这个注解的主要作用就是将主程序类所在包及所有子包下的组件到扫描到spring容器中。
3.2 @Import(AutoConfigurationImportSelector.class) 自动配置类引入
该注解的功能是将AutoConfigurationImportSelector这个类导入到spring容器中。AutoConfigurationImportSelector可以帮助SpringBoot应用,将所有符合条件的@Configuration配置类,都加载到当前SrpingBoot应用创建并使用的IOC容器(ApplicationContext)中
3.2.1 AutoConfigurationImportSelector.class类功能
- AutoConfigurationImportSelector通过selectImports方法,告诉Springboot需要导入哪些组件。
- selectImports中的核心方法为 getAutoConfigurationEntry
- getAutoConfigurationEntry 的核心为
getCandidateConfigurations(annotationMetadata, attributes); - getCandidateConfigurations方法包含两种读取形式,从META-INT/中的factories文件或者imports文件中读取需要自动配置注入的配置类类名
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
....
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
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);
}
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;
}
....
}
3.2.2 获取候选配置类的方式
getCandidateConfigurations 返回了所有需要自动配置类的全限定类名,两种加载方法,加载结果后期合并
- 获取自动配置类方法1:ImportCandidates.load方法,加载路径META-INF/spring/full-qualified-annotation-name.imports文件中的自动配置类类名。SpringBoot从2.7开始推荐使用这种imports方式。所以此次使用的SpringBoot 2.7.4 的spring-boot-autoconfigure包就已经包含了imports文件。
- 获取自动配置类的方法2:从所有jar的 META-INF/spring.factories 文件中读取自动装配的配置类名。现在使用的版本 2.7.4 的
- spring.factories 工厂文件必须采用Properties格式,其中键是接口或抽象类的完全限定名,值是用逗号分隔的实现类名列表。例如: example.MyService = example.MyServiceImpl1 example.MyServiceImpl2 在例子。MyService是接口的名称,MyServiceImpl1和MyServiceImpl2是两个实现。
- spring.factories中的 org.springframework.boot.autoconfigure.EnableAutoConfiguration类对应的那些配置类已经移动到了imports文件中了。但仍然保留了需要自动配置的监听器、处理器、过滤器等的自动装配类。
public final class ImportCandidates implements Iterable<String> {
private static final String LOCATION = "META-INF/spring/%s.imports";
private static final String COMMENT_START = "#";
private final List<String> candidates;
private ImportCandidates(List<String> candidates) {
Assert.notNull(candidates, "'candidates' must not be null");
this.candidates = Collections.unmodifiableList(candidates);
}
@Override
public Iterator<String> iterator() {
return this.candidates.iterator();
}
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> autoConfigurations = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
autoConfigurations.addAll(readAutoConfigurations(url));
}
return new ImportCandidates(autoConfigurations);
}
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
private SpringFactoriesLoader() {
}
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
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 {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
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) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
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;
}
三、@ComponentScan 包扫描
@ComponentScan注解具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决 定,在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到Spring Boot项 目主程序启动类所在包的具体位置
四、总结 springboot底层实现自动配置的步骤
- springboot应用启动;
- @SpringBootApplication起作用;
- @EnableAutoConfiguration;
- @AutoConfigurationPackage:这个组合注解主要是@Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中;
- @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories 和 .imports文件
|