SpringBoot作为当前Java开发的热门框架,有绞手架之称。“约定大于配置”也一直是SpringBoot的标签,那么,SpringBoot要实现自身优势,自动配置功不可没。
一、SpringBoot自动配置是什么
SpringBoot自动配置是指在应用程序启动时,SpringBoot根据classpath路径下的jar包自动配置应用程序所需的一系列bean和组件,从而减少开发者的配置工作,提高开发效率。文章源自设计学徒自学网-https://www.sx1c.com/45480.html
二、@Import注解
在剖析SpringBoot自动配置原理之前,我们先了解一下@Import注解的使用文章源自设计学徒自学网-https://www.sx1c.com/45480.html
1. 方式一: .class方式
定义两个类A、B,并将其加入到Spring IOC容器中:文章源自设计学徒自学网-https://www.sx1c.com/45480.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Data public class A{ private Integer id= 0 ; private String name= "classA" ; public void print(){ System.out.println( this .name); } } @Data public class B{ private Integer id= 1 ; private String name= "classB" ; public void print(){ System.out.println( this .name); } } |
创建一个配置类,并使用@Import注解将类A、B添加到 IOC 容器中:文章源自设计学徒自学网-https://www.sx1c.com/45480.html
1
2
3
4
|
@Import ({A. class ,B. class }) @Configuration public class ClassConfig{ } |
2.方式二:ImportSelector方式
该方法需要定义类来实现ImportSelector接口,并重写其中的selectImports( )方法,该方法的返回值是需要添加到IOC容器中的类的全限定类名数组:文章源自设计学徒自学网-https://www.sx1c.com/45480.html
1
2
3
4
5
6
7
8
|
@Data public class C{ private Integer id= 2 ; private String name= "classC" ; public void print(){ System.out.println( this .name); } } |
编写类来实现ImportSelector接口:文章源自设计学徒自学网-https://www.sx1c.com/45480.html
1
2
3
4
5
6
|
public class ImportSelectorTest implements ImportSelector{ @Override public String[] selectImports(AnnotationMetadata annotationMetada){ return new String[]{ "C.class" }; } } |
使用该类:文章源自设计学徒自学网-https://www.sx1c.com/45480.html
1
2
3
4
|
@Import ({ImportSelectorTest. class }) @Configuration public class ImportSelectorConfig{ } |
3.方式三:ImportBeanDefinitionRegistrar方式
定义一个类并实现ImportBeanDefinitionRegistrar接口,重写其中的registerBeanDefinitions( )方法,此方式可以自定义Bean在容器中的名称:文章源自设计学徒自学网-https://www.sx1c.com/45480.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@Data public class D{ private Integer id= 3 ; private String name= "ClassD" ; public void print(){ System.out.println( this .name); } } //定义类实现ImPortBeanDefinitionRegistrar接口,重写其中的registerBeanDefinitions()方法 public class ImportBeanDefinitionRegistrarTest{ @Override public void registerBeanDefninitions(AnnotationMetadata annotationMetadata,BeanDefinitionRegistry registry{ RootBeanDefinition rootBeanDefinition= new RootBeanDefinition(D. class ); registry.registerBeanDefinition( "自定义名称" ,rootBeanDefinition); } } //使用上面的类进行导入 @Import ({ImportBeanDefinitionRegistrarTest}) @Configuration public class ImportBeanDefinitionRegistrarConfig{ } |
三、SpringBoot自动配置原理解析
为了容易分析和理解,我们在IDEA中创建一个SpringBoot项目,创建过程省略,直接跳到该项目的主配置类进行分析:文章源自设计学徒自学网-https://www.sx1c.com/45480.html
1
2
3
4
5
6
7
8
|
@SpringBootApplication public class SpringBootTestApplication { public static void main(String[] args) { SpringApplication.run(SpringBootTestApplication. class , args); } } |
其中@SpringBootApplication注解是SpringBoot项目的重点,按住ctrl键进入其中,看到它由以下部分组成:文章源自设计学徒自学网-https://www.sx1c.com/45480.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@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 { //内容省略 } |
我们主要关注以下几个注解:
- @SpringBootConfiguration:标记当前类为配置类
- @EnableAutoConfiuration:开启自动配置
- @ComponentScan:扫描主类所在包及其子包、同级包中的Bean
1、@SpringBootConfiguration注解:标记当前类为配置类
1
2
3
4
5
6
7
8
9
10
11
|
@Target ({ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented @Configuration @Indexed public @interface SpringBootConfiguration { @AliasFor ( annotation = Configuration. class ) boolean proxyBeanMethods() default true ; } |
根据其源码可以知道,@SpringBootConfiguration注解包含@Configuration,所以其拥有@Configuration注解相似的功能,而@Configuration注解又包含@Companent注解,所以配置类也存在于IOC容器中。
2、@EnableAutoConfiguration注解:开启自动配置
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@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 {}; } |
根据其源码得出其主要由@AutoConfigurationPackages注解和@Import注解组成
@AutoConfigurationPackages注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@Target ({ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited @Import ({Registrar. class }) public @interface AutoConfigurationPackage { String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; } //其中的@Import注解导入了Registrar类,该类是AutoConfigurationPackages的静态类部类 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)); } } |
@Import({AutoConfigurationImportSelector.class})注解
这一步就用到了@Import注解使用方式中的第二中:实现ImportSelector接口
那么AutoConfigurationImportSelector接口何时如何被执行呢?
SpringBoot 启动时会使用 ConfigurationClassParser 来解析被 @Configuration 标识的配置类, 然后再处理这个类内部被其他注解修饰的情况, 比如 @Import 注解, @ComponentScan 注解,@Bean 注解等
若发现注解中存在 @Import(ImportSelector) 的情况下,就会创建一个相应的 ImportSelector 对象,并调用其 process 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> { return String.format( "Only %s implementations are supported, got %s" , AutoConfigurationImportSelector. class .getSimpleName(), deferredImportSelector.getClass().getName()); }); AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata); this .autoConfigurationEntries.add(autoConfigurationEntry); Iterator var4 = autoConfigurationEntry.getConfigurations().iterator(); while (var4.hasNext()) { String importClassName = (String)var4.next(); this .entries.putIfAbsent(importClassName, annotationMetadata); } } |
process方法又调用了getAutoConfigurationEntry方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (! this .isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { AnnotationAttributes attributes = this .getAttributes(annotationMetadata); List<String> configurations = this .getCandidateConfigurations(annotationMetadata, attributes); configurations = this .removeDuplicates(configurations); Set<String> exclusions = this .getExclusions(annotationMetadata, attributes); this .checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = this .getConfigurationClassFilter().filter(configurations); this .fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } } |
getAutoConfigurationEntry方法调用了getCandidateConfigurations方法:
1
2
3
4
5
|
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames( this .getSpringFactoriesLoaderFactoryClass(), this .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; } |
getCandidateConfigurations方法中使用了Spring Cor中的类SpringFactoriesLoader,该类的loadFactoryNames方法可以根据接口获取接口类的名称,这个方法返回的是类名的列表,loadFactoryNames方法会遍历整个springboot项目的classpath下的ClassLoader中所有jar包下的spring.factories文件。至此自动配置结束
总结
SpringBoot自动配置是SpringBoot的核心,所以了解SpringBoot的自动配置是非常有必要的,大家可以自行查找资料解释以下为什么不使用@ComponentScan注解替换@Import注解来进行类的导入
评论