我们知道 SpringBoot 的理念就是约定大于配置,这也使得我们在开发应用程序的过程更加便捷,以前的大量 XML 配置直接是噩梦呀,现在出现了 SpringBoot 明显降低了开发成本,而且大量的注解的使用帮我们省略掉了很多代码。本篇文章主要探究的是 SpringBoot 是如何实现自动配置并且如何加载配置 Bean 的,其实主要就是探究 @EnableAutoConfiguration 注解究竟发挥了怎样的作用。
@EnableAutoConfiguration @SpringBootApplication 其实是三个注解的组合体,这三个注解中 @Configuration 和 @ComponentScan 对我们来说并不陌生,今天我们主要探究的是 @EnableAutoConfiguration
。
1 2 3 4 5 6 7 8 9 @Target (ElementType.TYPE) @Retention (RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import (AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ... }
其中最关键的要属 @Import (AutoConfigurationImportSelector.class),借助 AutoConfigurationImportSelector,@EnableAutoConfiguration 可以帮助 SpringBoot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IoC 容器中。
借助于 Spring 框架原有的一个工具类:SpringFactoriesLoader 的支持,@EnableAutoConfiguration 可以智能的自动配置功效才得以大功告成!
在 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 类型的完整类名,比如:
1 xpu.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 进行实例化。
loadFactories 还是调用了 loadFactoryNames 与 instantiateFactory 方法。loadFactories 方法首先获取类加载器,然后调用 loadFactoryNames 方法获取所有的指定资源的名称集合、接着调用 instantiateFactory 方法实例化这些资源类并将其添加到 result 集合中。最后调用 AnnotationAwareOrderComparator.sort 方法进行集合的排序。
我们可以看一下 SpringBoot 的自动配置的 Bean 有哪些,见下图:
自动配置示例 下面结合一个例子来加深理解,例子展示的是当项目启动时如果某个类存在就自动配置这个 Bean,并且这个属性可以在 application.properties 中配置。新建一个 Maven 项目,pom.xml 文件如下:
pom.xml
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 28 29 30 31 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > xpu.tim</groupId > <artifactId > autoconfig-demo</artifactId > <version > 1.0-SNAPSHOT</version > <packaging > jar</packaging > <properties > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <maven.compiler.source > 1.8</maven.compiler.source > <maven.compiler.target > 1.8</maven.compiler.target > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-autoconfigure</artifactId > <version > 2.0.4.RELEASE</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <version > 2.0.4.RELEASE</version > <optional > true</optional > </dependency > </dependencies > </project >
Person.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package xpu.edu.tim;public class Person { private String name; public String sayName () { return "My Name is " + name; } public String getName () { return name; } public void setName (String name) { this .name = name; } }
PersonProperties.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package xpu.edu.tim;import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties (prefix = "person") public class PersonProperties { private static final String NAME = "Jock.Tim" ; private String name = NAME ; public String getName () { return name; } public void setName (String name) { this .name = name; } }
PersonAutoConfiguration.java
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 28 package xpu.edu.tim;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration @EnableConfigurationProperties (PersonProperties.class) @ConditionalOnClass (Person.class) @ConditionalOnProperty (prefix="person", value="enabled", matchIfMissing = true) public class PersonAutoConfiguration { @Autowired private PersonProperties personProperties; @Bean @ConditionalOnMissingBean (Person.class) public Person person () { Person person = new Person (); person.setName (personProperties.getName ()); return person; } }
spring.factories
1 org.springframework.boot.autoconfigure.EnableAutoConfiguration =xpu.edu.tim.PersonAutoConfiguration
最后使用 mvn package
将上面项目打包,使用 mvn install:install-file
命令将打包文件上传到本地 Maven 仓库进行测试,下面再新建一个 Maven 项目用于测试。
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.2.6.RELEASE</version > <relativePath /> </parent > <groupId > edu.tim</groupId > <artifactId > spring-boot-start</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > spring-boot-start</name > <description > Demo project for Spring Boot</description > <properties > <java.version > 1.8</java.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > xpu.tim</groupId > <artifactId > autoconfig-demo</artifactId > <version > 1.0-SNAPSHOT</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > <exclusions > <exclusion > <groupId > org.junit.vintage</groupId > <artifactId > junit-vintage-engine</artifactId > </exclusion > </exclusions > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
IndexController.java
1 2 3 4 5 6 7 8 9 10 11 @RestController @RequestMapping ("/") public class IndexController { @Autowired private Person person; @GetMapping public String index () { return person.sayName (); } }