SpringBoot深入自动配置

SpringBoot深入自动配置

引入

在上一篇Spring Boot与微服务基本介绍中,介绍了创建maven项目到运行springboot的基本过程。

其中使用主程序来启动SpringBoot

1
2
3
4
5
6
7
@SpringBootApplication
public class DeepSpringBootApplication {
public static void main(String[] args) {
// 使用SpringApplication.run方法来启动spring boot应用 其中参数有类和args
SpringApplication.run(DeepSpringBootApplication.class,args);
}
}

仅仅是添加了一个@SpringBootApplication注解,这个注解的作用是什么,原理是什么,接下来就通过源码简单看一下。

SpringBootApplication启动注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//点开@SpringBootApplication源码
@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注解的作用是为了声明别名
* AliasFor在下面代码中的作用就是通过别名引用在annotatin指定的类的特定方法
* 如果有兴趣可以参考AliasFor的官方文档
* https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AliasFor.html
* 比如这里就会指定 EnableAutoConfiguration的exclude方法
**/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
// 。。。其他的省略
}

在上面的@SpringBootApplication源码中,其中使用到了三个比较重要的基本注解@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan。下面分别来对这三个注解进行简单分析。

SpringBootConfiguration

1
2
3
4
5
6
7
8
9
10
11
/**
* 标示是一个spring boot应用的配置类,用作 @Configuratio的替代品
* 以便自动找到配置
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

其实这时SpringBootSpring@Configuration的注解的包装

Configuration

1
2
3
4
5
6
7
8
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
}

简单理解为,Configuration 注解上标注了@Component原注解,所以它也是一个spring组件,会通过扫描把标志这个注解的类作为spring的配置类加载到容器中。

实际上加载Configuration的方法有三种,自动扫描配置类只是其中的一种,其他两种是

  1. 通过AnnotationConfigApplicationContext类
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
// instantiate, configure and return bean ...
}
}

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
// 注册Bean
ctx.register(AppConfig.class);
ctx.refresh();
MyBean myBean = ctx.getBean(MyBean.class);
  1. 使用xml配置文件,将标注为@Configuration的类作为一个bean来声明在xml文件中。
1
2
3
4
<beans>
<context:annotation-config/>
<bean class="com.acme.AppConfig"/>
</beans>

标记@Configuration注解的类表示这个类中声明了一个或多个@Bean方法,可以交由spring容器处理。可以在运行时生成bean定义和bean之间的服务请求。

更加详细的参考Spring官网Configuration注解

EnableAutoConfiguration

EnableAutoConfiguration是spring boot中的注解,它的作用就是猜测并自动配置可能需要的bean,也就是自动配置功能。

1
2
3
4
5
6
7
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}

其中重要的两个注解@AutoConfigurationPackage@Import中的内容

AutoConfigurationPackage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 标注@AutoConfigurationPackage的注解可以使用Registrar进行注册
// @Import的作用就是引用配置类,将多个配置类放到一个主配置中
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

// 注册方法
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
// 注册bean定义信息
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 这里的packageName是标注这个注解的类的包的路径名称,如下图
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}

debug1

将主配置类的所在的包及下面的所有子包扫描到Spring容器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 以编程方式注册自动配置包名称.将给定的包【这里的是com.nanyin 】注册添加到已经注册的包名称中
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
// private static final String BEAN = AutoConfigurationPackages.class.getName();
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
}
else {
// 使用标准的类定义 GenericBeanDefinition 注册bean
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 注册bean
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}

@Import(AutoConfigurationImportSelector.class)

自动导入组件的选择器。将需要导入的组件以全类名的方式返回一个数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
//获取自动导入的内容的全类名,在这一步进行debug见下图
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}

代码中断点得出的自动导入的包名称:

代码导入1

通过方法getConstructorArgumentValues得到自动配置的包的名称,调用了SpringFactoriesLoader的方法,其中

1
2
3
4
5
6
7
8
9
10
11
12
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<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

最后通过文件中spring.factorys文件中的内容来获取最终的自动导入自动配置类包范围。

导入

导入包内容

ComponentScan

组件扫描指令,需要与@Configuration一起使用。与Spring提供的xml配置<context:component-scan>作用相同。

在SpringBootApplication注解中是这样定义ComponentScan的:

1
2
3
4
@ComponentScan(excludeFilters = { 
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})

excludeFilters的作用是指定哪些类型不符合组件扫描的条件,也就是排除掉指定的类。

-------------本文结束感谢您的阅读-------------

本文标题:SpringBoot深入自动配置

文章作者:NanYin

发布时间:2019年07月07日 - 12:07

最后更新:2019年08月12日 - 13:08

原始链接:https://nanyiniu.github.io/2019/07/07/2019-07-04-SpringBoot%E6%B7%B1%E5%85%A5%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AE/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。