SpringBoot自动配置原理

我们知道SpringBoot的理念就是约定大于配置,这也使得我们在开发应用程序的过程更加便捷,以前的大量XML配置直接是噩梦呀,现在出现了SpringBoot明显降低了开发成本,而且大量的注解的使用帮我们省略掉了很多代码。本篇文章主要探究的是SpringBoot是如何实现自动配置并且如何加载配置Bean的,其实主要就是探究@EnableAutoConfiguration注解究竟发挥了怎样的作用。

@EnableAutoConfiguration

@SpringBootApplication其实是三个注解的组合体,这三个注解中@Configuration和@ComponentScan对我们来说并不陌生,今天我们主要探究的是@EnableAutoConfiguration

1@Target(ElementType.TYPE)
2@Retention(RetentionPolicy.RUNTIME)
3@Documented
4@Inherited
5@AutoConfigurationPackage
6@Import(AutoConfigurationImportSelector.class)
7public @interface EnableAutoConfiguration {
8	...
9}

其中最关键的要属@Import(AutoConfigurationImportSelector.class),借助AutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器中。

借助于Spring框架原有的一个工具类:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自动配置功效才得以大功告成!

mark

在AutoConfigurationImportSelector类中可以看到通过SpringFactoriesLoader.loadFactoryNames() 把spring-boot-autoconfigure.jar/META-INF/spring.factories中每一个xxxAutoConfiguration文件都加载到容器中,spring.factories文件里每一个xxxAutoConfiguration文件一般都会有下面的条件注解。

  • @ConditionalOnClass : classpath中存在该类时起效
  • @ConditionalOnMissingClass : classpath中不存在该类时起效
  • @ConditionalOnBean : DI容器中存在该类型Bean时起效
  • @ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
  • @ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
  • @ConditionalOnExpression : SpEL表达式结果为true时
  • @ConditionalOnProperty : 参数设置或者值一致时起效
  • @ConditionalOnResource : 指定的文件存在时起效
  • @ConditionalOnJndi : 指定的JNDI存在时起效
  • @ConditionalOnJava : 指定的Java版本存在时起效
  • @ConditionalOnWebApplication : Web应用环境下起效
  • @ConditionalOnNotWebApplication : 非Web应用环境下起效

SpringFactoriesLoader

SpringFactoriesLoader属于Spring框架私有的一种扩展方案(类似于Java的SPI方案java.util.ServiceLoader),其主要功能就是从指定的配置文件META-INF/spring-factories加载配置,spring-factories是一个典型的java properties文件,只不过Key和Value都是Java类型的完整类名,比如:

1xpu.MyService = xpu.MyServiceImpl

对于@EnableAutoConfiguration来说,SpringFactoriesLoader的用途稍微不同一些,其本意是为了提供SPI扩展的场景,而在@EnableAutoConfiguration场景中,它更多提供了一种配置查找的功能支持,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfig.EnableAutoConfiguration作为查找的Key,获得对应的一组@Configuration类。

SpringFactoriesLoader是一个抽象类,类中定义的静态属性定义了其加载资源的路径public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”,此外还有三个静态方法

  • loadFactories:加载指定的factoryClass并进行实例化。
  • loadFactoryNames:加载指定的factoryClass的名称集合。
  • instantiateFactory:对指定的factoryClass进行实例化。

mark

loadFactories还是调用了loadFactoryNames与instantiateFactory方法。loadFactories方法首先获取类加载器,然后调用loadFactoryNames方法获取所有的指定资源的名称集合、接着调用instantiateFactory方法实例化这些资源类并将其添加到result集合中。最后调用AnnotationAwareOrderComparator.sort方法进行集合的排序。

我们可以看一下SpringBoot的自动配置的Bean有哪些,见下图:

mark

自动配置示例

下面结合一个例子来加深理解,例子展示的是当项目启动时如果某个类存在就自动配置这个Bean,并且这个属性可以在application.properties中配置。新建一个Maven项目,pom.xml文件如下:

mark

pom.xml

 1<?xml version="1.0" encoding="UTF-8"?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0"
 3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5    <modelVersion>4.0.0</modelVersion>
 6
 7    <groupId>xpu.tim</groupId>
 8    <artifactId>autoconfig-demo</artifactId>
 9    <version>1.0-SNAPSHOT</version>
10    <packaging>jar</packaging>
11
12    <properties>
13        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
14        <maven.compiler.source>1.8</maven.compiler.source>
15        <maven.compiler.target>1.8</maven.compiler.target>
16    </properties>
17
18    <dependencies>
19        <dependency>
20            <groupId>org.springframework.boot</groupId>
21            <artifactId>spring-boot-autoconfigure</artifactId>
22            <version>2.0.4.RELEASE</version>
23        </dependency>
24        <dependency>
25            <groupId>org.springframework.boot</groupId>
26            <artifactId>spring-boot-configuration-processor</artifactId>
27            <version>2.0.4.RELEASE</version>
28            <optional>true</optional>
29        </dependency>
30    </dependencies>
31</project>

Person.java

 1package xpu.edu.tim;
 2
 3public class Person {
 4    private String name;
 5
 6    public String sayName(){
 7        return "My Name is " + name;
 8    }
 9
10    public String getName() {
11        return name;
12    }
13
14    public void setName(String name) {
15        this.name = name;
16    }
17}

PersonProperties.java

 1package xpu.edu.tim;
 2
 3import org.springframework.boot.context.properties.ConfigurationProperties;
 4
 5@ConfigurationProperties(prefix = "person") //获取属性值
 6public class PersonProperties {
 7    private static final String NAME = "Jock.Tim";
 8
 9    private String name = NAME ;
10
11    public String getName() {
12        return name;
13    }
14
15    public void setName(String name) {
16        this.name = name;
17    }
18}

PersonAutoConfiguration.java

 1package xpu.edu.tim;
 2
 3import org.springframework.beans.factory.annotation.Autowired;
 4import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 5import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 6import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 7import org.springframework.boot.context.properties.EnableConfigurationProperties;
 8import org.springframework.context.annotation.Bean;
 9import org.springframework.context.annotation.Configuration;
10
11@Configuration
12//为带有@ConfigurationProperties注解的Bean提供有效的支持。
13//这个注解可以提供一种方便的方式来将带有@ConfigurationProperties注解的类注入为Spring容器的Bean。
14@EnableConfigurationProperties(PersonProperties.class)//开启属性注入,通过@autowired注入
15@ConditionalOnClass(Person.class)//判断这个类是否在classpath中存在,如果存在,才会实例化一个Bean
16@ConditionalOnProperty(prefix="person", value="enabled", matchIfMissing = true)
17public class PersonAutoConfiguration {
18    @Autowired
19    private PersonProperties personProperties;
20
21    @Bean
22    @ConditionalOnMissingBean(Person.class)//容器中如果没有Person这个类,那么自动配置这个Person
23    public Person person() {
24        Person person = new Person();
25        person.setName(personProperties.getName());
26        return person;
27    }
28}

spring.factories

1org.springframework.boot.autoconfigure.EnableAutoConfiguration=xpu.edu.tim.PersonAutoConfiguration

最后使用mvn package将上面项目打包,使用mvn install:install-file命令将打包文件上传到本地Maven仓库进行测试,下面再新建一个Maven项目用于测试。

 1<?xml version="1.0" encoding="UTF-8"?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 4    <modelVersion>4.0.0</modelVersion>
 5    <parent>
 6        <groupId>org.springframework.boot</groupId>
 7        <artifactId>spring-boot-starter-parent</artifactId>
 8        <version>2.2.6.RELEASE</version>
 9        <relativePath/> <!-- lookup parent from repository -->
10    </parent>
11    <groupId>edu.tim</groupId>
12    <artifactId>spring-boot-start</artifactId>
13    <version>0.0.1-SNAPSHOT</version>
14    <name>spring-boot-start</name>
15    <description>Demo project for Spring Boot</description>
16
17    <properties>
18        <java.version>1.8</java.version>
19    </properties>
20
21    <dependencies>
22        <dependency>
23            <groupId>org.springframework.boot</groupId>
24            <artifactId>spring-boot-starter</artifactId>
25        </dependency>
26        
27		<dependency>
28            <groupId>org.springframework.boot</groupId>
29            <artifactId>spring-boot-starter-web</artifactId>
30        </dependency>
31        
32        <!-- 引入上面的模块 -->
33        <dependency>
34            <groupId>xpu.tim</groupId>
35            <artifactId>autoconfig-demo</artifactId>
36            <version>1.0-SNAPSHOT</version>
37        </dependency>
38
39        <dependency>
40            <groupId>org.springframework.boot</groupId>
41            <artifactId>spring-boot-starter-test</artifactId>
42            <scope>test</scope>
43            <exclusions>
44                <exclusion>
45                    <groupId>org.junit.vintage</groupId>
46                    <artifactId>junit-vintage-engine</artifactId>
47                </exclusion>
48            </exclusions>
49        </dependency>
50    </dependencies>
51
52    <build>
53        <plugins>
54            <plugin>
55                <groupId>org.springframework.boot</groupId>
56                <artifactId>spring-boot-maven-plugin</artifactId>
57            </plugin>
58        </plugins>
59    </build>
60
61</project>

mark

IndexController.java

 1@RestController
 2@RequestMapping("/")
 3public class IndexController {
 4    @Autowired
 5    private Person person;
 6
 7    @GetMapping
 8    public String index(){
 9        return person.sayName();
10    }
11}

mark