什么是 Starter
Starter 是 Spring Boot 中的一个非常重要的概念,Starter 相当于模块,它能将模块所需的依赖整合起来并对模块内的 Bean 根据环境( 条件)进行自动配置。
使用者只需要依赖相应功能的 Starter,无需做过多的配置和依赖,Spring Boot 就能自动扫描并加载相应的模块并设置默认值,做到开箱即用
为什么使用 Starter
在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,麻烦至极。
如果我们将这些可独立于业务代码之外的功配置模块封装成一个个 starter,并在 starter 中设置好默认值,复用的时候只需要将其在 pom 中引用依赖即可,Spring Boot 为我们完成自动装配,做到开箱即用
。
Springboot 自动配置
SpringBoot 中的 starter 是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进 starter,应用者只需要在 maven 中引入 starter 依赖,Spring Boot 就能自动扫描各个 jar 包下 classpath 路径的 spring.factories 文件,加载自动配置类信息,加载相应的 bean 信息并启动相应的默认配置
。
Spring Boot 提供了针对日常企业应用研发各种场景的 spring-boot-starter 依赖模块。所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置
”的理念。
spring.factories
Spring Boot 会默认扫描跟启动类平级的包,如果我们的 Starter 跟启动类不在同一个主包下,需要通过配置 spring.factories 文件来配置生效,SpringBoot 默认加载各个 jar 包下 classpath 路径的 spring.factories 文件,配置的 key 为org.springframework.boot.autoconfigure.EnableAutoConfiguration
Starter 开发常用注解
注解使用已经大大方便我们开发,再也不需要写 xml 配置文件了,SpringBoot 经过查找 spring.factories 文件,加载自动配置类,而自动配置类中定义了各种运行时判断条件,如 @ConditionalOnMissingBean(A.class)等,只要 ioc 容器中没有指定的 A 类型的 bean 信息,该配置文件才会生效。
@Conditional 是 Spring4 新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册 bean。
属性映射注解
@ConfigurationProperties :配置文件属性值和实体类的映射
@EnableConfigurationProperties:和 @ConfigurationProperties 配合使用,把
@ConfigurationProperties 修饰的类加入 ioc 容器。
配置 bean 注解
@Configuration :标识该类为配置类,并把该类注入 ioc 容器
@Bean :一般在方法上使用,声明一个 bean,bean 名称默认是方法名称,类型为返回值。
条件注解
@Conditional:是根据条件类创建特定的 Bean,条件类需要实现 Condition 接口,并重写 matches 接口来构造判断条件。
@ConditionalOnBean :容器中存在指定 bean,才会实例化一个 Bean
@ConditionalOnMissingBean:容器中不存在指定 bean,才会实例化一个 Bean
@ConditionalOnClass:系统中有指定类,才会实例化一个 Bean
@ConditionalOnMissingClass:系统中没有指定类,才会实例化一个 Bean
@ConditionalOnExpression:当 SpEl 表达式为 true 的时候,才会实例化一个 Bean
@AutoConfigureAfter :在某个 bean 完成自动配置后实例化这个 bean
@AutoConfigureBefore :在某个 bean 完成自动配置前实例化这个 bean
@ConditionalOnJava :系统中版本是否符合要求
@ConditionalOnSingleCandidate:当指定的 Bean 在容器中只有一个,或者有多个但是指定了首选的 Bean 时触发实例化
@ConditionalOnResource:类路径下是否存在指定资源文件
@ConditionalOnWebApplication:是 web 应用
@ConditionalOnNotWebApplication:不是 web 应用
@ConditionalOnJndi:JNDI 指定存在项
@ConditionalOnProperty: 配置 Configuration 的加载规则
prefix :配置属性名称的前缀
value :数组,获取对应 property 名称的值,与 name 不可同时使用
name :数组,可与 prefix 组合使用,组成完整的配置属性名称,与 value 不可同时使用
havingValue :比较获取到的属性值与 havingValue 给定的值是否相同,相同才加载配置
matchIfMissing :缺少该配置属性时是否可以加载。如果为 true,没有该配置属性时也会正常加载;反之则不会生效
Full 全模式和 Lite 轻量级模式
@Configuration 参数 proxyBeanMethods:
Full 全模式(默认):@Configuration(proxyBeanMethods = true)
同一配置类下,当直接调用 @Bean 修饰的方法
注入的对象,则调用该方法会被代理
,从 ioc 容器中取 bean 实列,所以实列是一样的。即单实例对象,在该模式下 SpringBoot 每次启动都会判断检查容器中是否存在该组件
Lite 轻量级模式:@Configuration(proxyBeanMethods = false)
同一配置类下,当直接调用 @Bean 修饰的方法
注入的对象,则调用该方法不会被代理
,相当于直接调用一个普通方法,会有构造方法,但是没有 bean 的生命周期,返回的是不同的实例。
注:proxyBeanMethods 是为了让使用 @Bean 注解的方法
被代理。而不是 @Bean 的单例多例的设置参数。
测试例子这里不展示,可以下载我的代码查看
@Configuration(proxyBeanMethods = false)
public class AppConfig {
//放一份myBean到ioc容器
@Bean
public Mybean myBean() {
return new Mybean();
}
//放一份yourBean到ioc容器
@Bean
public YourBean yourBean() {
System.out.println("==========");
//注意:@Configuration(proxyBeanMethods = false):myBean()方法不代理,直接调用
//注意:@Configuration(proxyBeanMethods = true):myBean()方法代理,从ioc容器拿
return new YourBean(myBean());
}
}
复制代码
什么时候用 Full 全模式,什么时候用 Lite 轻量级模式?
Starter 命名规范
开发 Starter
1. 创建 Starter 项目
2. 添加依赖
<?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">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.ljw</groupId>
<artifactId>ljw-spring-boot-starter</artifactId>
<version>1.0</version>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 包含自动配置的代码-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- 配置文件点击可以跳转实体-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
复制代码
我们没有main入口,需要去除pom文件中maven打包插件spring-boot-maven-plugin
spring-boot-configuration-processor 作用:
spring-boot-configuration-processor 其实是一个注解处理器,在编译阶段干活的,一般在 maven 的声明都是 optional 为 true
你在 idea 里面可以点击 port,进到这个字段里面,还可以看到配置的提示信息
这是因为在你的资源文件里面有一个 spring-configuration-metadata.json 文件,这是 spring 配置的元数据,是 json 形式
3. 编写属性类
@ConfigurationProperties 可以定义一个配置信息类,和配置文件进行映射
@ConfigurationProperties(prefix = "ljw.config")
public class HelloProperties {
private String name = "hello 默认值!";
private int age = 8;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
复制代码
4. 自定义业务类
这里可以模拟一些获取了配置文件信息的进行业务操作的业务类
public class HelloService {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String hello() {
return "HelloService{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
复制代码
5. 编写自动配置类
命名规范:XxxAutoConfiguration
@Configuration(proxyBeanMethods = false)
// 当存在某个类时,此自动配置类才会生效
@ConditionalOnClass(value = {HelloService.class})
// 导入我们自定义的配置类,供当前类使用
@EnableConfigurationProperties(value = HelloProperties.class)
// 只有非web应用程序时此自动配置类才会生效
@ConditionalOnWebApplication
//判断ljw.config.flag的值是否为“true”, matchIfMissing = true:没有该配置属性时也会正常加载
@ConditionalOnProperty(prefix = "ljw.config", name = "flag", havingValue = "true", matchIfMissing = true)
public class HelloAutoConfiguration {
/**
* @param helloProperties 直接方法签名入参注入HelloProperties,也可以使用属性注入
* @return
*/
@Bean
@ConditionalOnMissingBean(HelloService.class)
//@ConditionalOnProperty(prefix = "ljw.config", name = "flag", havingValue = "true", matchIfMissing = true)
public HelloService helloService(HelloProperties helloProperties) {
HelloService helloService = new HelloService();
//把获取的信息注入
helloService.setName(helloProperties.getName());
helloService.setAge(helloProperties.getAge());
return helloService;
}
}
复制代码
注:这里配置一个 web 应用才能注入,并且 ljw.config.flag 的值是否为“true”或者不配置该 key 才能注入 HelloService 服务
6. 编写 spring.factories
把自动配置类 HelloAutoConfiguration 配置到 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的 key 下,springboot 会自动加载该文件并根据条件装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ljw.starter.config.HelloAutoConfiguration
复制代码
7. 编写配置提示文件(非必须)
additional-spring-configuration-metadata.json
配置 additional-spring-configuration-metadata.json 文件后,在开发人员的 IDE 工具使用个人编写的配置读取很有效的在application.properties
或application.yml
文件下完成提示。
我的配置:
{"properties": [
{
"name": "ljw.config.name",
"type": "java.lang.String",
"defaultValue": "hello 默认值!这里配置的是提示,真正默认值在Properties里面",
"description": "这是字符串名称啊."
},
{
"name": "ljw.config.age",
"defaultValue": 8,
"description": "这是int类型的年龄啊.",
"deprecation": {
"reason": "过时原因.",
"replacement": "替代key是:ljw.config.age22",
"level": "warning"
}
}
]}
复制代码
大家参考下面properties
表格进行配置上的理解。
deprecation
每个properties
元素的属性中包含的 JSON 对象可以包含以下属性:
spring-configuration-metadata.json
spring-configuration-metadata.json 代码量挺大的,为了方便我们可以通过 IDE 来生成,这里使用的是 idea。
在 idea 设置中搜索 Annotation Processors,接下来勾住 Enable annonation processing 就完成了。在编译打包后的文件中看到自动生成的 spring-configuration-metadata.json。这个文件不用我们编写
下面是自动生成的:
{
"groups": [
{
"name": "ljw.config",
"type": "com.ljw.starter.properties.HelloProperties",
"sourceType": "com.ljw.starter.properties.HelloProperties"
}
],
"properties": [
{
"name": "ljw.config.name",
"type": "java.lang.String",
"description": "这是字符串名称啊.",
"sourceType": "com.ljw.starter.properties.HelloProperties",
"defaultValue": "hello 默认值!这里配置的是提示,真正默认值在Properties里面"
},
{
"name": "ljw.config.age",
"type": "java.lang.Integer",
"description": "这是int类型的年龄啊.",
"sourceType": "com.ljw.starter.properties.HelloProperties",
"defaultValue": 8,
"deprecated": true,
"deprecation": {
"level": "warning",
"reason": "过时原因.",
"replacement": "替代key是:ljw.config.age22"
}
}
],
"hints": []
}
复制代码
测试 Starter
1. 前置环境
install 打包自定义 starter 项目:ljw-spring-boot-starter
新建项目:ljw-test-spring-boot-starter
2. 添加依赖
引入打好包的自定义 starter
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 测试web应用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--自定义satrter-->
<dependency>
<groupId>com.ljw</groupId>
<artifactId>ljw-spring-boot-starter</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
复制代码
3. 测试类
@Service
public class TestController implements CommandLineRunner {
/**
* 注入自定义starter服务
*/
@Resource
private HelloService helloService;
@Override
public void run(String... args) throws Exception {
System.out.println(helloService.hello());
}
}
复制代码
4. 修改配置文件
输入前缀可以看出已经有提示了
ljw.config.name=ljw hello!
ljw.config.age=99
ljw.config.flag=true
#不会注入
#ljw.config.flag=true1
# 可以看到哪些自动配置了
debug=true
复制代码
5. 运行程序打印
HelloService{name='ljw hello!', age=99}
复制代码
条件注入
如果没有 spring-boot-starter-web 依赖,不能注入服务 HelloService
如果配置了 ljw.config.flag,值不是 true,不能注入服务 HelloService;
如果不配置 ljw.config.flag,可以注入
6. 查看自动配置类生效的方法
通过启用 debug=true 属性,让控制台打印自动配置报告,这样就可以很方便地知道哪些自动配置类生效。
HelloAutoConfiguration matched:
- @ConditionalOnClass found required class 'com.ljw.starter.service.HelloService' (OnClassCondition)
- @ConditionalOnWebApplication (required) found 'session' scope (OnWebApplicationCondition)
- @ConditionalOnProperty (ljw.config.flag=true) matched (OnPropertyCondition)
HelloAutoConfiguration#helloService matched:
- @ConditionalOnMissingBean (types: com.ljw.starter.service.HelloService; SearchStrategy: all) did not find any beans (OnBeanCondition)
复制代码
作者:小伙子 vae
链接:https://juejin.cn/post/7047674475331977224
来源:稀土掘金
评论