spring boot最核心的特性就是他的自动化配置特性,极大的减少了构建一个spring web工程的工作量。那么你知道spring boot自动化配置的原理吗?
先直接自定义一个user-spring-boot-starter组件,感受下自动化配置的魅力。
pom依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.laowan</groupId>
<artifactId>user-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>user-spring-boot-starter</name>
<description>user-spring-boot-starter</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
添加配置属性
spring.user.enabled=false
spring.user.name=laowan
创建UserPorperties属性类使用配置属性
@Data
@ConfigurationProperties("spring.user")
public class UserPorperties {
private String name;
}
利用属性类UserPorperties构建UserClient对象
public class UserClient {
private UserPorperties userPorperties;
public UserClient(){
}
public UserClient(UserPorperties userPorperties){
this.userPorperties = userPorperties;
}
public String getName(){
return userPorperties.getName();
}
}
核心:创建UserAutoConfigure自动化配置类
@Configuration
@EnableConfigurationProperties(UserPorperties.class)
public class UserAutoConfigure {
@Bean
@ConditionalOnProperty(prefix = "spring.user",value ="enabled",havingValue = "true")
public UserClient userClient(UserPorperties userPorperties){
return new UserClient(userPorperties);
}
}
说明:
@ConfigurationProperties(“spring.user”)
读取以spring.user为前缀的属性文件,配置实体类
@EnableConfigurationProperties(UserPorperties.class)
将使用了@ConfigurationProperties 注解的类注入到spring容器中。
如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在IOC容器中是获取不到properties 配置文件转化的bean。
简单来说@EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入。
@ConditionalOnProperty 属性条件判断,判断指定的属性是否有指定的值,满足条件才会初始化bean。
类似UserAutoConfigure这样的自动化配置类是所有自动化配置组件的核心入口。
我们需要的就是在引入了user-spring-boot-starter的依赖后,在spring容器启动的时候,加载到这个自动化配置类,那么就可以初始化UserClient,完成自动化配置。
由于Spring boot默认扫描的是跟启动类平级的包。如果我们的Start跟启动类不在同一个主包下,那么就需要通过其他手段使得容器启动加载到UserAutoConfigure自动化配置类。
方式一:创建spring.factories属性配置文件
在user-spring-boot-starter工程下的/resources/META-INF目录下创建spring.factories属性配置文件,并在里面指定自动化属性配置类的全路径。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.laowan.user.autoconfig.UserAutoConfigure
这是由于在spring boot开启了自动化配置注解后,在容器启动时,会自动加载所有
/resources/META-INF下的spring.factories文件,读取org.springframework.boot.autoconfigure.EnableAutoConfiguration配置属性,组成一个集合,然后去遍历加载所有的自动化配置类。
建立demo工程测试:
1、引入user-spring-boot-starter的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.laowan</groupId>
<artifactId>user-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
2、配置属性文件
spring.user.enabled=true
spring.user.name=laowan
3、编写单元测试类
@SpringBootTest
@Slf4j
class DemoApplicationTests {
@Autowired
UserClient userClient;
@Test
void userClientTest() {
log.info(userClient.getName());
}
}
方式二:通过定义@EnableXXX注解来加载自动化配置文件
在user-spring-boot-starter工程下,新建@EnableUserClient注解,其中最核心的是通过
@Import注解注入了UserAutoConfigure.class。这样在引用工程的启动类上只要添加了@EnableUserClient注解,那么就会加载到UserAutoConfigure自动化配置类
```javascript
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({UserAutoConfigure.class})
public @interface EnableUserClient {
}
测试:
@SpringBootApplication
@EnableUserClient
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
先注释掉spring.factories中的配置项,然后执行单元测试,正确打印出配置属性spring.user.name的值,说明我们的自动化配置生效。
方式三:通过@SpringBootApplication注解指定扫描的基础包路径
在测试工程demo中,配置@SpringBootApplication的属性scanBasePackages
@SpringBootApplication(scanBasePackages = {"com.laowan.demo","com.laowan.user.autoconfig"})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
通过@SpringBootApplication注解的scanBasePackages 属性,指定扫描的包路径。
这里注意,一定要首先指定自己工程的根路径,然后再执行自动化配置类的包路径。
不然就只会扫描自动化配置类的包路径,自己工程就不会扫描导致启动出错。
测试:
执行单元测试,发现配置属性spring.user.name的值仍然可以正常打印,说明我们的自动化配置成功。
三种方式的比较:
三种加载自动化配置的方式,其核心都是解决Spring boot工程启动只会默认扫描跟启动类平级的包,导致其他不同包下的自动化配置类XXXAutoConfigure.class加载不到的问题。
方式一是通过spring.factories文件中配置org.springframework.boot.autoconfigure.EnableAutoConfiguration属性指定自动化配置类XXXAutoConfigure.class的全路径,是最主流的方式。
方式二通过自定义@EnableXXX注解并结合@Import注解加载自动化配置文件,在很多组件中也很常见,比如:
@EnableResourceServer
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ResourceServerConfiguration.class})
public @interface EnableResourceServer {
}
@EnableDiscoveryClient
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableDiscoveryClientImportSelector.class})
public @interface EnableDiscoveryClient {
boolean autoRegister() default true;
}
方式三主要是对自动化配置原理的一种验证,实际项目中不推荐。
自动化配置原理核心:
就是想办法在spring容器启动的时候,扫描到自动化配置类,然后根据属性类和条件注解去声明Bean,完成自动化配置。
在user-spring-boot-starter工程的/resources/META-INF目录下创建spring-configuration-metadata.json
{
"properties": [
{ "name": "spring.user.name",
"defaultValue": "laowan",
"description": "用户名称.",
"type": "java.lang.String"
},
{ "name": "spring.user.enabled",
"defaultValue": false,
"description": "是否启用user组件.",
"type": "java.lang.Boolean"
}
]
}
1、spring官方自己定义的starter组件,命名规则:spring-boot-starter-组件名
如:
spring-boot-starter-web
spring-boot-starter-jdbc
spring-boot-starter-security
非官方定义的start组件,命名规则:组件名-spring-boot-starter
如:
mybatis-spring-boot-starter
2、自动化配置类命名:XXXAutoConfiguration
3、starter组件的常规目录结构分析
一般分为2个工程,一个xxx-spring-boot-starter工程,通过spring.provides指定依赖服务模块;
一个xxx-spring-boot-starter-autoconfigure模块,通过定义spring.factories指定自动化配置文件加载路径,定义spring-configuration-metadata.json实现自动化配置。
在构建starter的过程中,涉及到一些注解
@EnableAutoConfiguration 开启自动化配置功能,包含在@SpringBootApplication注解中,可以通过exclude属性,过滤掉一些不需要开启自动化配置的组件。
@Import 通过快速导入的方式实现把实例加入spring的IOC容器中,@Import只能用在类上
@ConfigurationProperties(“spring.user”) 加载前缀为spring.user的属性去配置当前类,但是并不会加载到spring容器中,需要配合@Component或者@EnableConfigurationProperties去使用。
@EnableConfigurationProperties(UserPorperties.class) 使 使用了@ConfigurationProperties注解配置的类生效,也就是注入到spring容器中
条件注解@Conditional
可以放在加了@Configuration的配置类上面,也可以放在使用@Bean定义bean的时候。用来判断是否开启配置或是否注入bean。
@ConditionalOnBean:当容器中有指定的Bean的条件下
@ConditionalOnClass:当类路径下有指定的类的条件下
@ConditionalOnExpression:基于SpEL表达式作为判断条件
@ConditionalOnJava:基于JVM版本作为判断条件
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置
@ConditionalOnMissingBean:当容器中没有指定Bean的情况下
@ConditionalOnMissingClass:当类路径下没有指定的类的条件下
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件下
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径下是否有指定的资源
@ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者在有多个Bean的情况下,用来指定首选的Bean
@ConditionalOnWebApplication:当前项目是Web项目的条件下
1、自动化配置的原理:
(1)、加载自动化配置类
通过@EnableAutoConfiguration开启自动化配置机制,原理是通过类加载器去扫描目录下所有spring.factories文件,读取org.springframework.boot.autoconfigure.EnableAutoConfiguration属性,然后去加载XXXAutoConfigure自动化配置类。这个是通用的加载方式,适合批量默认自动开启的组件。
针对某些特定组件,没有定义spring.factories文件,则需要通过在启动类上添加@EnableXXX的注解,通过@Import导入指定的自动化配置类,这种方式适合单一控制,默认不开启自动化配置的组件。
(2)、根据读取到的自动化配置类,完成相关配置过程
XXXAutoConfigure自动化配置类中根据spring boot相关注解,读取相关属性文件,并根据@Conditional条件注解判断是否开启自动化配置,是否实例化Bean。
2、自动化配置类加载的三种方式
3、怎么自定义一个starter组件以及相关的规范
4、自动化配置过程中,常见注解的说明
spring boot自动化配置的原理,你明白了吗?
觉得有用,记得点赞加关注。
实战代码Git地址:https://github.com/StarlightWANLI/auto-config.git