写点什么

还在 curd 吗?封装属于自己的 Spring-Boot-Starter

  • 2023-03-09
    湖南
  • 本文字数:6873 字

    阅读完需:约 23 分钟

什么是 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 轻量级模式?

  • 当在你的同一个 Configuration 配置类中,注入到容器中的 bean 实例之间有依赖关系时,建议使用 Full 全模式

  • 当在你的同一个 Configuration 配置类中,注入到容器中的 bean 实例之间没有依赖关系时,建议使用 Lite 轻量级模式,以提高 springboot 的启动速度和性能

Starter 命名规范

  • Spring 官方 Starter 通常命名为 spring-boot-starter-{name}如:spring-boot-starter-web

  • Spring 官方建议非官方 Starter 命名应遵循{name}-spring-boot-starter 的格式:如 mybatis-spring-boot-starter。

开发 Starter

1. 创建 Starter 项目


  • 新建项目后,要删除main启动类

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.propertiesapplication.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. 测试类

@Servicepublic 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=99ljw.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

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
还在curd吗?封装属于自己的Spring-Boot-Starter_Java_做梦都在改BUG_InfoQ写作社区