07-SpringBoot自动配置深入
springboot自动配置原理深入
要点:springboot的核心配置类中的注解@SpringBootApplication,这是一个核心注解。该注解主要源代码如下
...
import ...
@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 {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
.......
}
可以看出,@SpringBootApplication注解是:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {
@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}),
@Filter(type = FilterType.CUSTOM,classes ={AutoConfigurationExcludeFilter.class})
}
)
这三个注解的组合注解,这些注解具体作用如下所示
-
@SpringBootConfiguration注解,其源码部分如下
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; } /** 可以看到该注解的源码中,除了三个注解标配以外,就是一个@Configuration,就是说该注解的功能和 @Configuration一样,作用是声明这个类是一个配置类,只不过@SpringBootConfiguration声明的是一个核心 配置类,所以为什么我们称springboot应用的主程序类是一个核心配置类 */
-
@ComponentScan注解,@SpringBootApplication注解中的该注解的属性中添加了两个扫描器
@ComponentScan( excludeFilters = { @Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}), @Filter(type = FilterType.CUSTOM,classes ={AutoConfigurationExcludeFilter.class}) } ) //该注解就是指定扫描那些包,将包中的类自动spring框架自动创建对象,作为组件存放到容器中 //上面的注解中的属性定义了两个扫描器,具体是什么可以回顾spring的注解,因为这个注解是spring框架的
-
@EnableAutoConfiguration注解,这个是我们需要关心的核心注解。找该注解的部分源代码
package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; @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 {}; } /* 可以看到该注解中是: @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) 这两个注解的合成 */
-
@AutoConfigurationPackage:翻译过来:自动配置包。部分源码如下
package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar; import org.springframework.context.annotation.Import; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class}) public @interface AutoConfigurationPackage { String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; } /* 可以看到该注解中只有一个特别注解: @Import({Registrar.class}) @Import注解表示给容器中导入一个组件,这里导入的是一个Registrar.class 这里可能是@Import的更高级用法,可以自己取看一下,这里不探究 */
-
我们查看一下Registrar类,
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])); } public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata)); } } /* 可以看到Registrar类是AutoConfigurationPackages类的一个静态内部类: 上面源码中有两个方法,主要作用是利用这个类给容器批量注册组件,具体怎么 实现,我们可以给第一个方法中的打上断点一探究竟如下: */
断点示意图
然后DEBUG启动类调试,可以看到执行的方法有两个参数: registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) 其中AnnotationMetadata metadata//翻译过来就是注解的源信息 /* 就是@AutoConfigurationPackage注解标注在哪个类, 而这里是@SpringBootApplication注解标注在哪里就是该注解标注在哪里 这里是标注在主程序类上,如下图 */
方法中有这样一段语句: (String[]) (new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]) /* PackageImports(metadata)).getPackageNames() PackageImports表示要导入的包里的东西 表示将注解的源信息metadata拿进来,获取到注解的包名 这里就是获取主程序类所在的包名以及这个包的各个子包的包名 我们可以看一下包名,复制 new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames() 右键->选择Evaluate Expression->粘贴进去,evaluate,可以看到是com.studymyself 是主程序类所在包名 .toArray(new String[0]这句语句就是将拿到的各个包名存储到字符串数组中 */
这就解释了为什么规定我们在默认情况下要将需要作为组件的各类创建在和主程序类所在的包以及该包的子包下
-
-
注解@Import({AutoConfigurationImportSelector.class}):@AutoConfigurationPackage注解的@Import({Registrar.class})注解规定了要批量导入组件的哪些包,而这个注解就规定要批量导入的哪些组件,查看AutoConfigurationImportSelector类,源码部分如下:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry(); private static final String[] NO_IMPORTS = new String[0]; private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class); private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude"; private ConfigurableListableBeanFactory beanFactory; private Environment environment; private ClassLoader beanClassLoader; private ResourceLoader resourceLoader; private AutoConfigurationImportSelector.ConfigurationClassFilter configurationClassFilter; public AutoConfigurationImportSelector() { } //核心方法就是这个 public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } } ...... /* 利用getAutoConfigurationEntry(annotationMetadata);给容器批量导入一些组件 selectImports方法通过获取AnnotationMetadata annotationMetadata注解参数执行 方法getAutoConfigurationEntry(annotationMetadata) 表示自动创建获取所扫描包中的所有组件对象,然后执行 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); 将获取的组件转换到数组中进行返回 */
-
添加断点进行debug调试,如图
当程序抛到断点处时,一步一步往下执行,直到执行下面第二条语句时: List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); configurations = this.removeDuplicates(configurations); 可以查看调试页面,看到configurations这个集合中有130个组件要装配到容器中 怎么获取的,是从getCandidateConfigurations方法中获取的,我们再重新debug一遍,当执行到该方法时,进入该方法中执行
-
-
```yaml
#(candidate:(竞选或求职的)候选人,申请人; 投考者; 应试者; 参加考试的人; 被认定适合者; 被认定有某种结局者;)(attribute v. 把…归因于; 认为…是由于; 认为是…所为(或说、写、作);n.属性; 性质; 特征;)
当进入到this.getCandidateConfigurations(annotationMetadata, attributes)方法中执行后(获取候选的配置),可以看到是通过获取spring的工厂类加载器(getSpringFactoriesLoaderFactoryClass())来完成的需要装配的组件类信息的获取,核心语句:
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
进入loadFactoryNames方法中查看,如下
```
就是说
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader)
方法帮我们获取了所有应该装配的组件信息,是默认扫描整个项目的"META-INF/spring.factories"中获取这个文件,加载其中的信息。这主要的依赖加载进来的jar包中的"META-INF/spring.factories",有些jar包的META-INF目录下是没有spring.factories。
但是最核心的是spring-boot-autoconfigure-2.2.5.RELEASE.jar这个jar包中是有spring.factories
我们点开其中的spring.factories,可以看到# Auto Configure注解下面的信息就是我们之前调试时,configurations集合中存储的,是囊括了所有场景的配置类,都是***AutoConfiguration结尾的。
就是说是已经再配置文件中写死了的springboot一启动就加载的所有场景的配置类作为组件,然后这些配置类中又为各自场景所需要的组件进行组件装配配置。
虽然130个场景的自动配置类在启动的时候默认加载,但是由于这些场景的所需包以及自动配置类中的的条件装配注解@Conditional限制了是否向容器中装配这些组件,所以最终会按需进行配置的。所以我们在容器中看不到不需要的类的组件
-
总结
```java 1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件 2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类 3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件 4、从META-INF/spring.factories位置来加载一个文件。 默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件 spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
```java @Bean @ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件 @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件 public MultipartResolver multipartResolver(MultipartResolver resolver) { //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。 //SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范 // Detect if the user has created a MultipartResolver but named it incorrectly return resolver; } 给容器中加入了文件上传解析器;
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
```java @Bean @ConditionalOnMissingBean public CharacterEncodingFilter characterEncodingFilter() { }
## 总结: - SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration - 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定 - 生效的配置类就会给容器中装配很多组件 - 只要容器中有这些组件,相当于这些功能就有了 - 定制化配置 - - 用户直接自己@Bean替换底层的组件 - 用户去看这个组件是获取的配置文件什么值就去修改。 **xxxxxAutoConfiguration ---> 组件 --->** **xxxxProperties里面拿值 ----> application.properties**