目录
2、@EnableAutoConfiguration注解:实现自动配置
2.1、@AutoConfigurationPackage 自动配置包
2.2、@Import(AutoConfigurationImportSelector.class) 自动配置组件导入选择器
1、引导加载自动配置类
我们都知道在创建一个 Spring Boot应用 的时候,一般类文件路径的最外层都会有一个启动类,也叫主配置类。通过主配置的 main() 方法可以启动 Spring Boot 应用程序,如果你足够细心的话,可以发现主配置上有一个 @SpringBootApplication 注解
@SpringBootApplication
public class HelloWorldMainApplication {
public static void main(String[] args) {
// 启动Spring应用
SpringApplication.run(HelloWorldMainApplication.class, args)
}
}
@SpringBootApplication:这个注解会被标记在 Spring Boot 的启动类/主配置类上,在 IDEA 中点开这个注解的源码,我们可以发现它是一个嵌套注解,里面包含了很多其他的注解,其实这些注解有的就是用于引导和按需加载自动配置类的。
@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 {
}
表1. @SpringBootApplication 注解包含的嵌套注解及其作用
注解 | 作用 |
@Target(ElementType.TYPE) | JDK提供的注解,主要用来限制注解的作用类型,ElementType.TYPE 表示适用于类、接口或枚举,此外还有FIELD、METHOD 等等 |
@Retention(RetentionPolicy.RUNTIME) | JDK提供的注解,用于限制注解的作用范围。RetentionPolicy.RUNTIME 表示注解会被编译在 class 的类文件并且保留到运行时的虚拟机中,所以可以通过反射获取,此外还有 SOURCE(注:在编译时就会被丢弃,无法通过反射获取)和 CLASS(注:默认值,注解会被编译在class的类文件中,但是不会保留到运行时的虚拟中)两种范围 |
@Documented
| JDK的注解,表示该注解可以被javadoc文档和类似的工具所记录 |
@Inherited
| JDK的注解,表示该注解类型的子注解可以自动继承父注解的所有注解 |
@ComponentScan | Spring的组件扫描注解,一般和@Configuration注解一起使用,指定Spring扫描注解的package。如果没有指定包,那么默认会扫描此配置类所在的package。
|
@Filter | 这个注解起到过滤器的作用,通常和 @ComponetScan注解一起使用,过滤掉不需要的自动配置类 |
@SpringBootConfiguration | 底层就是 @Configuration 注解,表示被注解的类是一个配置类 |
@EnableAutoConfiguration | 这个注解是 SpringBoot 实现自动配置的基础 |
以下这张图片粗略地展示了 @SpringBootApplication 注解的底层结构:
从上面的图片可以看出,@SpringBootApplication 注解最外层的嵌套注解包含了 @EnableAutoConfiguration、@SpringBootConfiguration 和 @ComponentScan 注解,从名字中可以看出 @EnableAutoConfiguration 注解,好像是和自动配置有关的,那么我们先来看看这个注解
2、@EnableAutoConfiguration注解:实现自动配置
点开 @EnableAutoConfiguration 注解的源码,我们可以发现很多嵌套注解,其中有两个注解非常重要:
@AutoConfigurationPackage 自动配置包,指定了默认的包规则
@Import(AutoConfigurationImportSelector.class) 自动配置组件导入选择器
实际上,就是这两个注解实现的自动配置。
2.1、@AutoConfigurationPackage 自动配置包
我们先来看看看 @AutoConfigurationPackage 注解的源码
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
可以看到 @AutoConfigurationPackage 注解中有一个@Import(AutoConfigurationPackages.Registrar.class) 注解,查看其源码会发现有一个 Registrar类,这个类里面有一个 registerBeanDefinitions() 方法,通过源码注释和方法名称我们可以得知,这个方法用于批量注册 bean 定义
/**
* 批量注册Bean定义
* 将指定的一个包下的所有组件导入进来?
* 1、利用Registrar给容器中导入一系列的组件
* 2、相当于Registrar把HelloWorldMainApplication主配置类包下的组件批量注册进Spring容器
*/
// AnnotationMetadata metadata:注解原信息 —> AutoConfigurationPackage -> 主配置类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 通过注解原信息拿到包名封装到数组中,然后注册进去
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
}
通过反射获取注解原信息,拿到注解配置所在的包名,然后批量注册 bean 定义。那么是不是真的是这样做的呢,我们可以打个断点调试看看。
启动应用之后可以看到,扫描的包名正好是主配置类所在的包下面
2.2、@Import(AutoConfigurationImportSelector.class) 自动配置组件导入选择器
这个注解其实是 Spring 用于决定导入哪些自动配置类的
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 利用getAutoConfigurationEntry()给容器中批量导入组件
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
/**
* 1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入组件
* 2、调用getCandidateConfigurations(annotationMetadata, attributes);获取所有要导入容器
* 中的类
* 3、利用工厂加载 Map<String, List<String>> loadSpringFactories(ClassLoader
* classLoader) 得到所有的组件
* 4、从 "META-INF/spring.factories"来加载一个文件
* 默认扫描我们系统里面所有 "META-INF/spring.factories"位置的文件
* spring-boot-autoconfigure-2.4.4.RELEASE.jar包里面也有META-INF/spring.factories
* 扫描的就是这个文件里的 130个自动配置类
* org.springframework.boot.autoconfigure.EnableAutoConfiguration
* 启动时128个场景的应用默认全部加载,实际只有128个能按需配置 @ConditionalOnClass
* 5、按需加载,条件装配(@Conditional)
*/
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 = 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;
}
查看此处,我们可以发现结果刚好就是130个
当然,并不是所有的自动配置类都会配置进 Spring 容器,因为很多时候不是所有的自动配置都能用得上的。如果不需要的自动配置也配置进容器了,在运行程序时就会造成不必要的资源消耗
3、按需开启自动配置项目
-
启动时130个场景的应用默认全部加载,实际只有128个能按需配置 @ConditionalOnClass
-
按需加载,条件装配(@Conditional)
打开 Spring 自动配置依赖包,可以发现这里面有很多的自动配置类,我们随机查看其中一个就能发现一些以 @ConditionalOn 开头的注解,比如 @ConditionalOnClass、@ConditionalOnMissingBean 等等
就是这些注解实现的按需加载自动配置,例如:
@ConditionalOnClass(Advice.class) 表示指定的类存在才会加载这个自动配置,@ConditionalOnProperty 则表示指定的属性存在才加载这个配置类,
我们现在知道了 Spring 是按需加载的自动配置类,那么我们能不能修改Spring的默认配置呢,答案是肯定的
4、修改默认配置
Spring Boot 默认底层配置所有必要的组件,但是如果用户有自定义配置,那么会以用户配置的优先
@Bean
@ConditionalOnBean(MultipartResolver.class) // 当容器中有这个组件
@ConditionalOnMissingBean(name = // 容器中没有这个名字的组件 DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// 给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找
// SpringMvc multipartResolver,防止用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
5、SpringBoot最佳实践
- 引入场景依赖
- 查看SpringBoot自动配置了哪些
-
自己分析,引入场景的配置一般都生效
-
配置文件中 debug=true 开启自动配置报告,negative不生效 / positive生效
-
-
是否需要修改
-
参照文档修改配置项:Common Application Properties
-
自己分析,xxxProperties 绑定了配置文件的哪些
-
自定义加入或者替换组件:
-
@Bean、@Component
-
-
自定义器 xxxCustomizer
-