先直接自定义一个user-spring-boot-starter组件,感受下自动化配置的魅力。
构建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()); } }
4、执行结果
说明我们的自动化配置生效
方式二:通过定义@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、自动化配置过程中,常见注解的说明