爱上极客's Archivers

From yan on 2018-04-11 16:22:51

Spring Boot源码分析——自动配置

Spring Boot以spring-boot-starter-xx命名的模块都是“开箱即用”模块,意思是说,当开发者完成依赖添加后(即pom文件的依赖添加),这个功能就会自动创建和注入到上下文中,不需要再编写麻烦的配置,只需要提供参数属性即可。十分的方便,那么如此巧妙的开箱即用是怎么实现的呢?

本文将探索其中的奥秘,笔者模拟编写自动装载JDBC的过程:

<img class="aligncenter wp-image-1930" src="http://www.i3geek.com/wp-content/uploads/2018/04/springboot自动配置.png" alt="" width="783" height="735" />

为了方便学习,将自动配置分为三部分进行理解:

  1. 业务类(待检测类)DemoJDBCService:通常使用Maven引入依赖,或Jar包等方式引入类。

  2. 自动配置类JDBCAutoConfiguration:判断业务类是否存在,若存在对业务类进行初始化、装载,比如完成读取参数、初始化等操作。

  3. 扫描加载自动配置类AutoConfigurationImportSelector:调用自动配置类。


业务类


自动配置的触发点,被检测的核心业务类,若该类存在才会进行自动配置,实现“开箱即用”的功能,否则不执行配置。

例子中,采用最简单的业务,代码如下:
public class DemoJDBCService {
public String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public void connect(){
//链接操作
}
}

注意:这个类不能被component-scan扫描到。该bean应该由自动配置进行装载,如果被扫描到顺序不同,不符合逻辑会产生错误。(这其实是不可避免的“缺陷”,读者可以尝试在自己的项目中扫描 org.spring 会发现项目无法启动)

通俗点理解,假设本类中还依赖JDBC的相关库,如果没有加载相关库,被component-scan扫描到必然会报错NoClassDefFoundError。如果是自动装载类加载,装载类会先判断是否存在相关库,存在再加载 这时就不会报错。

自动配置类


自动配置分为三步:

(1)从application.properties中读取参数;

(2)完成对业务类Bean的初始化、装载;

(3)配置spring.factories

读取参数


方法一:

Spring 提供了一个注解用于导入配置文件中的数据 — @Value 。
@Value("${key:value}")
private String url;

参数的key代表properties中的key,value是默认值,即若不存在该key时的取值。

注意:直接填写会当做字符串处理,如果想设null应填写#{null}

方法二:(本例采用该方法)

创建属性参数类JDBCProperties,用来读取并管理参数
@ConfigurationProperties(prefix = "custom")//属性前缀
public class JDBCProperties {
public static final String DEFAULT_URL = "localhost";
public String url = DEFAULT_URL;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}

在属性参数文件application.properties 中,读取前缀为custom的属性。如针对上述代码,配置中应写:
custom.url = localhost:3306....

初始化装载


这是最核心的地方

  1. 首先利用@Conditional等注解判断业务类是否存在。若存在则继续。

  2. @EnableConfigurationProperties注解来加载配置参数对象

  3. 编写Resolver方法,利用参数初始化装载业务Bean


@Configuration
@ConditionalOnClass({ DemoJDBCService.class }) //判断业务类是否存在
@EnableConfigurationProperties(JDBCProperties.class) //加载配置参数对象
public class JDBCAutoConfiguration {
@Resource
private JDBCProperties jdbcProperties;
//开始装载Bean
@Bean
@ConditionalOnMissingBean(DemoJDBCService.class)
@ConditionalOnProperty(name = "custom.url.enabled", matchIfMissing = true) //配置中加个属性,灵活控制开关
public DemoJDBCService jdbcResolver() {
DemoJDBCService jdbcService = new DemoJDBCService();
jdbcService.setUrl(jdbcProperties.getUrl());
return jdbcService;
}
}

spring.factories


配置spring.factories,添加上刚刚定义的自动配置类。用于运行时扫描自动加载类时使用,否则将无法加载该配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.i3geek.springboot.demo.autoConfig.JDBCAutoConfiguration

这里是采用系统的自动装载扫描类EnableAutoConfiguration,也可以用户自行编写。(具体后面会详细介绍)

扫描加载配置


自动扫描的过程可以自行编写,本例中采用spring中自带的流程进行源码讲解。

  1. 主函数通过@EnableAutoConfiguration注解,利用其内@Import方法导入利用AutoConfigurationImportSelector类

  2. 利用AutoConfigurationImportSelector类完成spring.factories文件的扫描,从而加载配置。


@EnableAutoConfiguration


@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 {};
}

@Import , 这个注解可以导入一个配置类到另一个配置类中。在 Spring4.2 中对这个注解进行了加强,可以直接将一个类加入Spring容器。那么只要写上 @Import(AutoConfigurationImportSelector.class) 即可。

AutoConfigurationImportSelector类


该类是实现与ImportSelector接口,该接口作用与注解 @Import类似。
public interface ImportSelector {

/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}

其中核心方法selectImports,根据 importingClassMetadata 的值,从带有注解 @Configuration 的类中选择并返回合适的类名数组,将其导入 Spring 容器。因此,查看AutoConfigurationImportSelector类中的ImportSelector方法,就是导入自动配置的地方
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//关键,获得类名列表
configurations = this.removeDuplicates(configurations);//去除重复
Set exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
}
}

可见本方法中,主要是getCandidateConfigurations 方法获取类名,之后经过一些处理,把名返回完成spring的导入。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());//从META-INF/spring.factories中获取
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方法中通过 SpringFactoriesLoader.loadFactoryNames 扫描 spring.factories 文件获得类名。这里就不再深入看了。

Main函数测试


@RestController
@EnableAutoConfiguration //扫描自动配置类,并进行加载
public class JDBCAutoConfigDemo {
@Resource
private DemoJDBCService jdbcService;

@RequestMapping("/")
String test() {
return "url:"+ jdbcService.getUrl();
}
//main函数省略
}

查看完整版本: Spring Boot源码分析——自动配置

Tags: SpringBoot


©爱上极客