不论在工作中,亦或是求职面试,Spring Boot已经成为我们必知必会的技能项,如今的各行各业都在飞速的拥抱这个已经不是很新的Spring启动框架。
当然,作为Spring Boot的精髓,自动配置原理的工作过程往往只有在“面试”的时候才能用得上,但是如果在工作中你能够深入的理解Spring Boot的自动配置原理,将无往不利。
Spring Boot的出现,得益于“习惯优于配置”的理念,没有繁琐的配置、难以集成的内容(大多数流行第三方技术都被集成),这是基于Spring 4.x提供的按条件配置Bean的能力。
自动配置原理
Spring Boot关于自动配置的源码在spring-boot-autoconfigure-x.x.x.x.jar中:

当然,自动配置原理的相关描述,官方文档貌似是没有提及。不过我们不难猜出,Spring Boot的启动类上有一个@SpringBootApplication注解,这个注解是Spring Boot项目必不可少的注解。那么自动配置原理一定和这个注解有着千丝万缕的联系!
@EnableAutoConfiguration
@SpringBootApplication是一个复合注解或派生注解,在@SpringBootApplication中有一个注解@EnableAutoConfiguration,翻译成人话就是开启自动配置,其定义如下:
而这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelector的selectImports()方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factories的jar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件。
这个spring.factories文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔,如下图所示:
这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(...)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。
自动配置生效
每一个XxxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,常见的条件注解有如下几项:
@ConditionalOnBean:当容器里有指定的bean的条件下。
@ConditionalOnMissingBean:当容器里不存在指定bean的条件下。
@ConditionalOnClass:当类路径下有指定类的条件下。
@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。
@ConditionalOnProperty:指定的属性是否有指定的值,比如@ConditionalOnProperties(prefix=”xxx.xxx”, value=”enable”, matchIfMissing=true),代表当xxx.xxx为enable时条件的布尔值为true,如果没有设置的情况下也为true。
以ServletWebServerFactoryAutoConfiguration配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081,是如何生效的(当然不配置也会有默认值,这个默认值来自于org.apache.catalina.startup.Tomcat)。
在ServletWebServerFactoryAutoConfiguration类上,有一个@EnableConfigurationProperties注解:开启配置属性,而它后面的参数是一个ServerProperties类,这就是习惯优于配置的最终落地点。
在这个类上,我们看到了一个非常熟悉的注解:@ConfigurationProperties,它的作用就是从配置文件中绑定属性到对应的bean上,而@EnableConfigurationProperties负责导入这个已经绑定了属性的bean到spring容器中(见上面截图)。那么所有其他的和这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些XxxxProperties类,它与配置文件中定义的prefix关键字开头的一组属性是唯一对应的。
至此,我们大致可以了解。在全局配置的属性如:server.port等,通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties注解导入到Spring容器中。
而诸多的XxxxAutoConfiguration自动配置类,就是Spring容器的JavaConfig形式,作用就是为Spring 容器导入bean,而所有导入的bean所需要的属性都通过xxxxProperties的bean来获得。
可能到目前为止还是有所疑惑,但面试的时候,其实远远不需要回答的这么具体,你只需要这样回答:
Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。
什么是starter
Starter可以理解为一个可拔插式的插件,提供一系列便利的依赖描述符,您可以获得所需的所有Spring和相关技术的一站式服务。应用程序只需要在maven中引入starter依赖,SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。用一句话描述,就是springboot的场景启动器。
下面是Spring官方提供的部分starter,全部的请参考官网:
自定义starter
官方定义的starter命名都是spring-boot-starter-xxx,我们自己定义的一般都 xxx-spring-boot-starter,新建一个id-gengerate-spring-boot-starter项目
导入springboot 包spring-boot-starter
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--主要是为了生成spring-configration-metadata.json文件,方便idea跳转-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
1.创建配置类,定义一个映射配置新的的类IdProperties和LockieProperties,添加注解ConfigurationProperties("machine.id"),我们的配置文件以machine.id开头,比如mathine.id.name=boot-order,另外一个配置文件是已spring.lockie开头的配置文件
package com.lockie.starter.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author: lockie
* @Date: 2020/11/3 15:10
* @Description: id生成配置类
*/
@Data
@ConfigurationProperties("machine.id")
public class IdProperties {
private String name;
}
package com.lockie.starter.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author: 邹细良
* @Date: 2020/11/3 18:21
* @Description:
*/
@Data
@ConfigurationProperties("spring.lockie")
public class LockieProperties {
private String prefix;
private String suffix;
}
2.创建一个接口用来生成对应的业务,IdService里面有个getId方法用来生成自定义的ID
package com.lockie.starter.service;
import com.lockie.starter.config.IdProperties;
import lombok.Data;
/**
* @author: lockie
* @Date: 2020/11/3 15:19
* @Description: 生成ID接口
*/
@Data
public class IdService {
private IdProperties idProperties;
/**
* 获取ID
* @return
*/
public String getId() {
return idProperties.getName() + "-" + System.currentTimeMillis();
}
}
package com.lockie.starter.service;
import com.lockie.starter.config.LockieProperties;
import lombok.Data;
/**
* @author: lockie
* @Date: 2020/11/3 18:23
* @Description:
*/
@Data
public class LockieService {
LockieProperties lockieProperties;
public String hello(String name) {
return lockieProperties.getPrefix() + "-" + name + "-" + lockieProperties.getSuffix();
}
}
3.创建配置类,重要的配置参数
@Configuration:配置类
@EnableConfigurationProperties():开启某属性类,里面封装的是属性信息,相当于@ConfigurationProperties加载application.properties里面的信息,并用@Component将类交由spring管理
@ConditionalOnClass():当这个类存在时,再进行下面的步骤,否则就不进行下面的步骤了
@AutoConfigureAfter():当自动配置完成后,再执行里面的类
@ConfigurationOnMissBean():当这个bean不存在时,再添加bean类
@Bean:添加bean类
/**
* @author: lockie
* @Date: 2020/11/3 15:17
* @Description: 获取ID配置类
*/
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(IdProperties.class)
public class IdAutoConfiguation {
@Autowired
private IdProperties idProperties;
@Bean
public IdService idService() {
IdService idService = new IdService();
idService.setIdProperties(idProperties);
return idService;
}
}
/**
* @author: lockie
* @Date: 2020/11/3 18:26
* @Description:
*/
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties({LockieProperties.class})
public class LockieAutoConfiguration {
@Autowired
LockieProperties lockieProperties;
@Bean
public LockieService lockieService() {
LockieService lockieService = new LockieService();
lockieService.setLockieProperties(lockieProperties);
return lockieService;
}
}
4.最重要的一步,在resources目录下创建META-INF目录,并添加文件spring.factories。在这个文件中配置EnableAutoConfiguration,具体如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lockie.starter.config.IdAutoConfiguation,\
com.lockie.starter.config.LockieAutoConfiguration
测试自定义的Starter
我们在之前的项目boot-order-service中测试我们的starter, boot-order-service项目pom增加id-generate-spring-boot-starter包
<dependency> <groupId>com.lockie-starter</groupId> <artifactId>id-generate-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
项目包路径一样可能还需要在boot-order-starter启动类上增加扫描id-generate-spring-boot-starter项目包路径
@SpringBootApplication(scanBasePackages = "com.lockie.starter")
boot-order-service项目配置文件增加配置参数
machine.id.name=boot-order spring.lockie.prefix=Hello! spring.lockie.suffix=Nice to meet you
到这里我们在boot-order-service服务中就可以使用id-generate-spring-boot-starter项目中的services了,并且只需要使用@Autowired即可
@SpringBootTest
@ContextConfiguration(classes = BootOrderServiceApplication.class)
class BootOrderServiceApplicationTests {
@Test
void contextLoads() {
}
@Autowired
IdService idService;
@Autowired
LockieService lockieService;
@Test
public void testIdGenerate() {
String id = idService.getId();
System.out.println("生成ID:" + id);
String str = lockieService.hello("小明");
System.out.println("打招呼:"+ str);
}
}
运行一下查看结果,看到结果是我们想要的,至此我们自定义一个starter就成功了,总结一下就是要创建3个类1个配置文件,xxProperties,xxService,xxAutoConfiguation和spring.factories