@SpringBootApplication详解
1 .简介
在Spring Boot的学习中难免需要接触源码,而入手及时从Spring Boot项目启动类开始入手。项目启动类非常简单,仅仅存在一个注解@SpringBootApplication以及一个运行参数为被该注解标注类run函数。
@SpringBootApplication
public class BiuApplication {
public static void main(String[] args) {
SpringApplication.run(BiuApplication.class, args);
}
}
对于该启动类的分析,就从这个Spring Boot的核心注解开始入手。
2 .核心注解@SpringBootApplication
字面分析,这个注解是标注一个Spring Boot应用。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration//引入EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...//具体参数暂时忽略
}
进入到这个注解后,可以发现该注解由四个元注解,以及其他三个注解组成分别是:@SpringBootConfiguration、
@EnableAutoConfiguration(自动配置有关的核心注解,主要分析)、
@ComponentScan(包扫描)(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
2.1 Spring的配置类@SpringBootConfiguration
字面分析,这是一个Spring Boot的配置类。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
从他的源码来看,除了元注解之外,它仅仅被@Configuration
注解所标注,那么可以理解@SpringBootConfiguration
为他仅仅就是一个配置类。
@Configuration 源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {...}
再分析@Configuration的源码,该注解为Spring中的配置类注解,其中被@Component标注为Spring组件,意味着他被注册到IOC容器。
因此,套用官方文档的答案:@SpringBootConfiguration表示一个类提供 Spring Boot 应用程序@Configuration。可以用作 Spring 标准@Configuration注释的替代方法,以便可以自动找到配置(例如在测试中)。
2.2 开启自动配置@EnableAutoConfiguration !!!
这个注解是Spring Boot的自动装配的核心。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)//自动配置的引入选择器实现
public @interface EnableAutoConfiguration {...}
除了四个元注解,这个注解被两个注解所标注:
@AutoConfigurationPackage
、
@Import(AutoConfigurationImportSelector.class)
那么我们先直接往下分析
2.2.1 自动配置包@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {...}
从注解来看,@AutoConfigurationPackage
中使用注解@Import
(@Import:的作用)导入了AutoConfigurationPackages.Registrar.class到容器中,那么来分析这个类,进入到这个内部类Regisrar:
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));
}
}
该类引入的重点在于方法registerBeanDefinitions()
:
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
首先先分析方法体中所调用的方法register()
的第二个参数
PackageImports(metadata).getPackageNames().toArray(new String[0])
进入到类PackageImports的构造方法:
PackageImports(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
packageNames.add(basePackageClass.getPackage().getName());
}
if (packageNames.isEmpty()) {
packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
}
this.packageNames = Collections.unmodifiableList(packageNames);
}
在这个构造方法中将元数据即启动类AnnotationMetadata metadata
经过处理
1.获取标签注解信息,注解信息里面的 basePackages
和 basePackageClasses
是否有数据。
basePackages、 basePackageClasses
为注解@AutoConfigurationPackage
中的属性。
2.如果没有数据则获取注解所在的类的名字目录,放到List中
获得packageNames
属性也就是启动类所在的包。
回到Registrar
中的registerBeanDefinitions()
方法中register()
方法的第二个参数即为启动类所在的包的名称,并且使用数组来进行表示。
在分析register()
方法,register()源码如下:
private static final String BEAN = AutoConfigurationPackages.class.getName();
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
//BeanDefinitionRegistry registry 其中放方法boolean containsBeanDefinition(String beanName);是为了判断是否已经注册了`AutoConfigurationPackages`的类路径所对应的`bean(AutoConfigurationPackages)`
if (registry.containsBeanDefinition(BEAN)) {
BasePackagesBeanDefinition beanDefinition =(BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
beanDefinition.addBasePackages(packageNames);
}
else {
registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
}
}
这个方法的if
语句为判断registry
这个参数中是否已经注册了AutoConfigurationPackages
的类路径所对应的bean(AutoConfigurationPackages)
。如若已经被注册,则把上面分析的第二个参数所获取的包(启动类所在的包的名称)添加到这个bean
的定义中。如若没有,则注册这个bean
并且把包名设置到该bean
的定义中。
小结:@AutoConfigurationPackage
就是添加该注解的类所在的包作为自动配置包进行管理。他的实现就是依赖于工具类AutoConfigurationPackages中的内部类Registrar对所标注的包进行注册。
2.2.2 导入 自动配置导入选择器@Import(AutoConfigurationImportSelector.class)
这个@import
使用了2.2.1中@import的用法中的通过ImportSelector 方式导入的类,所以我们进入到该类,直接找到selectImports
方法。在这个用法中,所返回的字符串数组为所有的将要被导入的类的全类名。那么知道这个方法是做什么的,就开始分析这个方法。
再分析selectImports
方法,selectImports()
源码如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {//selectImports方法会被springboot自动调用,从而得到他返回的全类名的字符串数组,然后把对应类的bean对象注入到ioc容器中
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
从return开始分析,autoConfigurationEntry自动配置实体中List的属性configurations将被返回。autoConfigurationEntry是通过方法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);
}
根据return所返回的内容,返回的是一个使用属性configurations所生成的自动配置实体,configurations是使用getCandidateConfigurations()
获取候选配置所得到的。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())//load 加载的意思
.getCandidates();
Assert.notEmpty(configurations,
"No auto configuration classes found 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;
}
从以上源码可以发现,在springboot3
之后,已完全放弃对META-INF/spring.factories
的读取。而是从传的AutoConfiguration.class
取的类名称。即org.springframework.boot.autoconfigure.AutoConfiguration
。拼接之后 ,META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
(.imports配置文件)。(2.7升级到3.0后的变化)
读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
中的org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
DispatcherServletAutoConfiguration
配置类中还有配置类DispatcherServletConfiguration
,其中还有方法dispatcherServlet(WebMvcProperties webMvcProperties)
添加了@bean
注解springboot
会继续解析,直到把@Bean
注解的方法都解析到,然后执行这些方法,把返回值注入到ioc
容器中,因此自动配置的核心在配置文件里
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)//如果当前环境存在DispatchServlet类,则注入,否则不注入
//注意 DispatchServlet类 是引用web启动依赖后会有的
public class DispatcherServletAutoConfiguration {
...
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
...
}
2.2.3总结!!!
@SpringBootApplication
是一个组合注解,其中有@EnableAutoConfiguration
也是一个组合注解,@EnableAutoConfiguration
有`@Import(AutoConfigurationImportSelector.class)
,导入了AutoConfigurationImportSelector.class
类,AutoConfigurationImportSelector.class
实现了selectImports
方法,经过层层调用最终读取一个配置文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
(注意,springboot2.7
前读取的是spring.factories
文件,2.7-3.0兼容两个,3.0之后只有.imports
)这个配置文件中写了一堆的全类名,其中有一个是org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
,它是完成DispatcherServlet
这个对象的自动注入的,DispatcherServletAutoConfiguration
类中有@AutoConfiguration
标明是一个自动配置类,@ConditionalOnClass(DispatcherServlet.class)
用来去设置bean
注册的条件(如果环境里有DispatcherServlet.class
这个配置类,DispatcherServletAutoConfiguration
这个自动配置类就生效,否则就不生效,注意引入web
启动依赖就有DispatcherServlet.class
这个配置类),因此说引入了web
启动依赖springboot
就自动注入了一个DispatcherServlet
。