SpringBoot源码解析(一) @SpringBootApplication注解解析
我们新建一个SpringBoot项目都会引入一个pom然后新建一个这样启动类,就能运行一个JAVA项目,而不需要像传统的SSM框架那样编写各种XML以及配置项,那么SpringBoot是如何完成我们之前那些事情的呢?
pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
启动类
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
新建的启动类只做了两件事
- 添加了一个@SpringBootApplication注解
- 调用了SpringApplication类的run方法
首先我们先分析@SpringBootApplication注解做了什么
@SpringBootApplication注解上组合了其他注解,红色框子中的元注解不用关注,让我们看看其他几个注解各自有什么作用
一、@SpringBootConfiguration
@SpringBootConfiguration组合了@Configuration
1.1 @Configuration的作用
@Configuration是Spring的中的注解,表明当前类是一个配置类并且它组合@Component注解,意味也将会注册为bean
由此我们可以得出的结论的是我们新建的启动类是一个配置类,并且会作为一个Bean导入到Spring容器中
二 @EnableAutoConfiguration
@EnableAutoConfiguration组合了@AutoConfigurationPackage注解和向容器中导入了AutoConfigurationImportSelector这个类,我们分开来看这两个注解的作用
2.1 @AutoConfigurationPackage
@AutoConfigurationPackage 向容器中导入了AutoConfigurationPackages.Registrar这个类,
进入Registrar这个类看他究竟干了啥
2.1.1AutoConfigurationPackages.Registrar
Registrar实现了ImportBeanDefinitionRegistrar接口和DeterminableImports(测试使用不用关心)接口
如果不清楚ImportBeanDefinitionRegistrar作用的可以看下面的案例
2.1.1.1 ImportBeanDefinitionRegistrar接口
这个接口配合@Import注解向容器中注册Bean的 使用案例:
定义测试Bean
public class Teacher {
private String[] name;
public Teacher(String... name) {
this.name = name;
}
public void hello(){
System.out.println(name + "你好啊!");
}
}
注册Bean信息
public class AutoConfiguredTeacherRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
//设置Bean的类别
beanDefinition.setBeanClass(Teacher.class);
//添加构造参数
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, "张三");
//注册bean
registry.registerBeanDefinition("teacher", beanDefinition);
}
}
导入注册类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(AutoConfiguredTeacherRegistrar.class)
public @interface EnableTeacherBean {
}
测试启动类
@EnableTeacherBean
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class);
context.getBean(Teacher.class).hello();
}
}
所以我们接下来就看看Registrar向容器中注册了什么
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
入参出创建了一个PackageImport类,调取了getPackageName方法 我们看看这个类是什么作用
这个类的构造方法中会通过类工具获取当元数据所在类的包路径,也就启动类的包路径
继续进入register方法
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
//如果容器中包含了AutoConfigurationPackages这个类
if (registry.containsBeanDefinition(BEAN)) {
//获取Bean信息
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition
.getConstructorArgumentValues();
//添加并合并构造参数
constructorArguments.addIndexedArgumentValue(0,
addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
//设置Bean类型
beanDefinition.setBeanClass(BasePackages.class);
//添加构造参数
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
//注册Bean信息
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
这个方法是向容器中注入了一个BasePackages类,并且将根路径作为入参传入进去
所以@AutoConfigurationPackage的作用是向容器中注册了一个BasePackages的类,并且这个类存储了启动类的路径
虽然Spring和SpringBoot没有使用到这个类 但是可以作为三方Starter中运用到了,比如Mybatis集成SpringBoot的自动配置
AutoConfiguredMapperScannerRegistrar在注册Mapper配置信息的时候会读取启动类的包路径作为扫描路径
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
} else {
MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
//获取启动类的根路径
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
//设置注解类型
builder.addPropertyValue("annotationClass", Mapper.class);
//设置包扫描路径
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
//注册Bean信息
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
2.2 AutoConfigurationImportSelector
熟悉Spring的都知道Aware接口是获取Spring加载中的一些信息没有实质作用,Ordered接口是用来排序的,所以只剩下了一个接口DeferredImportSelector,那么这个接口是用来干什么的?
它是 ImportSelector
的子接口,它的文档注释原文和翻译
A variation of ImportSelector that runs after all @Configuration beans have been processed. This type of selector can be particularly useful when the selected imports are @Conditional . Implementations can also extend the org.springframework.core.Ordered interface or use the org.springframework.core.annotation.Order annotation to indicate a precedence against other DeferredImportSelectors . Implementations may also provide an import group which can provide additional sorting and filtering logic across different selectors.
ImportSelector 的一种扩展,在处理完所有 @Configuration 类型的Bean之后运行。当所选导入为 @Conditional 时,这种类型的选择器特别有用。
实现类还可以扩展 Ordered 接口,或使用 @Order 注解来指示相对于其他 DeferredImportSelector 的优先级。
实现类也可以提供导入组,该导入组可以提供跨不同选择器的其他排序和筛选逻辑。
DeferredImportSelectorHandler中维护了DeferredImportSelector, DeferredImportSelectorGrouping调用getImportGroup获取所有SelectorHandler拿到待处理的DeferredImportSelector(其中就包含AutoConfigurationGroup),接着调用 process 方法,AutoConfigurationGroup 会调用我们导入的AutoConfigurationImportSelector类的getAutoConfigurationEntry方法,拿到要引入的配置集合(Entry类型的集合),最后遍历这个集合逐个解析配置类。
接下来让我们看下是怎么拿到所有自动的配置的
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
//1.获取@EnableAutoConfiguration注解所有属性信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//2.加载自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//3.移除重复配置
configurations = removeDuplicates(configurations);
//4.根据注解的属性信息获取被排除的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
//5.移除所有被排除的自动配置类
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
进入getCandidateConfigurations看看是如何拿到需要导入的自动配置类
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//使用Spring的SPI机制获取META-INF/spring.factories下所有key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类型
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;
}
获取@EnableAutoConfiguration注解的类信息
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
所以AutoConfigurationImportSelector类的作用就是获取所有META-INF/spring.factories下的自动配置类信息,之后这些自动配置类会被装配到Spring容器中,所以即便没有任何配置文件,SpringBoot的Web应用都能正常运行。
三、@ComponentScan
@ComponentScan这个注解在Spring中的作用是指定包扫描的根路径,让Spring来扫描指定包及子包下的组件,也可以不指定路径,默认扫描当前配置类所在包及子包里的所有组件,所以Springboot只会扫描启动类当前包和以下的包
excludeFilters属性的作用是指定包扫描的时候根据规则指定要排除的组件,type = FilterType.CUSTOM是自定义过滤,classes 指定的类要实现TypeFilter接口,在match方法中可以获取当前扫描到的类的信息,比如注解、类名和类路径。接下来我们看看这两个类的match的作用
3.1 TypeExcludeFilter
它是一种扩展机制,能让我们向IOC容器中注册一些自定义的组件过滤器,以在包扫描的过程中过滤它们。主要用于测试
3.2AutoConfigurationExcludeFilter
它是用来判断是否是配置类和自动配置类
总结
1. 当前类被@Configuration标注,所以启动类也是一个配置类
2. 当前类被@ComponentScan标注,默认扫描当前配置类所在包及子包里的所有组件,所以Springboot只会扫描启动类当前包和以下的包
3. 当前类被@EnableAutoConfiguration标注,导入了AutoConfigurationImportSelector类,这个类会利用Spring的SPI机制扫描加载META-INF/spring.factories下所有key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的自动配置类
转载自:https://juejin.cn/post/7201364247501930557