@SpringBootApplication
@SpringBootApplication是一个3合一注解:
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {
...
}
@SpringBootConfiguration
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@ComponentScan
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
导入主程序类所在的包及其子包@AutoConfigurationPackage
翻译过来叫自动配置包?指定了默认的包规则,看源码
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
它导入了一个Registrar,这是个内部类,点进去看源码,debug调试
利用Registrar给容器中导入一系列组件,将指定的一个包com.atguigu.boot
下的所有组件导入进来
默认是主程序类MainApplication 所在包下。
导入spring.factories当中的配置类@Import(AutoConfigurationImportSelector.class)
- 利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
- 调用List
configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类 130个 - 利用工厂加载 Map
> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件 - 从META-INF/spring.factories位置来加载一个文件。 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件 ,META-INF/spring.factories,位于spring-boot-autoconfigure-2.3.4.RELEASE.jar包当中META-INF/spring.factories
- 利用工厂加载 Map
- 调用List
点进AutoConfigurationImportSelector去看源码selectImports方法
@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<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);
}
getCandidateConfigurations方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}
loadFactoryNames方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
loadSpringFactories方法
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
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();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
加载了一个配置文件叫“META-INF/spring.factories”
在这一步拿到所有的配置,返回上层我们看一共有130个
默认扫描我们当前系统里面所有spring.factories位置的文件,spring.factories位于spring-boot-autoconfigure-2.3.4.RELEASE.jar包当中META-INF/spring.factories,spring.factories文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
spring.factories,这里当然也包括我们熟悉的伙伴spring-boot-starter-web
的自动配置类包
总结以下,SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
- 每个自动配置类是个配置类
@Configuration
- 每个自动配置类按照条件进行生效
@ConditionalOnXXXX
- 生效的配置类就会给容器中装配很多组件@Bean,容器放了这些组件,相当于这些功能就有了
更激动人心的是,自动配置类会为我们绑定参数配置文件,类注解
@EnableConfigurationProperties(ServerProperties.class)
有趣的自动配置MultipartResolver
源码解读:@Bean
//容器中有这个类型组件
@ConditionalOnBean(MultipartResolver.class)
//容器中没有这个名字 multipartResolver 的组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器名字不叫multipartResolver,不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
给容器中加入了文件上传解析器;并且返回的resolver叫multipartResolver,因为我们的bean方法名叫multipartResolver
相当于强制的给用户配置的视图解析器重命名为multipartResolver
配置文件参数默认映射
@EnableConfigurationProperties
@EnableConfigurationProperties(ServerProperties.class)
标注在类上
@ConfigurationProperties
@ConfigurationProperties(prefix = “server”)标注在参数类上
所以用户还可以去看这个组件是获取的yaml/properties配置文件什么值,prefix是什么,就去修改@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
/**
* Network address to which the server should bind.
*/
private InetAddress address;
@NestedConfigurationProperty
private final ErrorProperties error = new ErrorProperties();
...
}
允许用户可以定制化组件
用户可以直接自己@Bean替换底层Spring默认的组件
有趣的组件CharacterEncodingFilter
底层的ConditionalOnMissingBean意味着Spring的一种设计模式:条件装载,当此Bean,CharacterEncodingFilter
不存在时才生效,意味着会向容器当中放置一个Spring默认的CharacterEncodingFilter
。SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}