【Spring Boot】如何封装一个starter

1,033 阅读5分钟

本文主要内容:

  • 如何封装一个spring-boot-starter

在编码实现之前、首先考虑如下几个问题:

  • 如何读取配置文件中的配置信息?
  • 如何使用读取到的配置信息?
  • 如何自动配置到应用中?

读取配置文件

springboot应用配置文件常用的有ymlproperties文件格式、不管怎么是采用那种配置方式、我们都需要把这些信息封装到一个对象中、所以为了我们方便统一管理配置信息、需要一个封装此starter信息的类。除此之外还需要考虑一个事情、就是如何将配置信息注入到这个类上。

以自定义序列化json字符串为例(功能: 给序列化添加自定义前缀和后缀)。首先创建一个maven工程。命名格式如下:

  • 如果是我们开发者自己开发的 starter 组件(即属于第三方组件),那么命名规范是 {name}-spring-boot-starter
  • 如果是 SpringBoot 官方自己开发的组件,则命名为 spring-boot-starter-{name}

我的取名为super-json-spring-boot-starterpom.xml文件中引入如下依赖:

<dependencies>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
          <version>2.3.8.RELEASE</version>
      </dependency>
      <!-- 编写配置文件时idea提示配置信息-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-configuration-processor</artifactId>
          <version>2.3.8.RELEASE</version>
          <optional>true</optional>
      </dependency>
      <!-- json -->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.73</version>
      </dependency>
      <!-- 自动配置 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-autoconfigure</artifactId>
          <version>2.3.8.RELEASE</version>
      </dependency>
  </dependencies>

接着定义一个java类封装配置文件信息:

@ConfigurationProperties(prefix = "super.json")
public class SuperJsonProperties {
    public static final String DEFAULT_PREFIX = "super";
    public static final String DEFAULT_SUFFIX = "json";
​
    private String prefix = DEFAULT_PREFIX;
    private String suffix = DEFAULT_SUFFIX;
​
    public String getPrefix() {
        return prefix;
    }
​
    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
​
    public String getSuffix() {
        return suffix;
    }
​
    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

代码说明:

  • @onfigurationProperties(prefix = "super.json"):是将配置文件中的前缀为super.json的这一模块的配置信息封装到这个类里面。

为什么不用@value?

虽然@Value注解也可以读取配置文件的信息、但是如果模块配置信息较多的情况下、使的代码冗余。主要原因有如下几点:

  • @ConfigurationProperties 更适合于绑定一组相关的配置属性,尤其是当属性数量较多或需要复杂类型的绑定时。
  • @ConfigurationProperties 的配置类提供了集中管理和类型安全的优势,便于维护和调试。
  • @Value 更适合于注入少量的简单配置属性,或需要使用 SpEL 表达式进行复杂处理的场景。
  • @Value 注解直接在代码中使用,如果属性较多,维护起来会比较麻烦。

使用配置信息

当读取到这些信息之后、如何使用呢?这就很简单咯、想怎么用就怎么用、代码如下:

public class JsonService {
​
    private String prefixName;
​
    private String suffixName;
​
    public String objectToJson(Object object) {
        return prefixName + JSON.toJSONString(object) + suffixName;
    }
​
    public String getPrefixName() {
        return prefixName;
    }
​
    public void setPrefixName(String prefixName) {
        this.prefixName = prefixName;
    }
​
    public String getSuffixName() {
        return suffixName;
    }
​
    public void setSuffixName(String suffixName) {
        this.suffixName = suffixName;
    }
}

注入到Spring容器中

当目前为止、我们读取了数据信息、在自定义的service中实现了我们需要的业务逻辑。接下来就需要把这个service注入到 IOC 容器中、是需要使用到地方自动注入即可,因此我们需要一个配置类将其配置到IOC容器中。

@org.springframework.context.annotation.Configuration
@ConditionalOnClass(JsonService.class)
@EnableConfigurationProperties(SuperJsonProperties.class)
public class Configuration {
    private SuperJsonProperties jsonProperties;
​
    public Configuration(SuperJsonProperties jsonProperties) {
        this.jsonProperties = jsonProperties;
    }
​
    @Bean
    @ConditionalOnMissingBean(JsonService.class)
    @ConditionalOnProperty(name = "super.json.enable",matchIfMissing = true)
    public JsonService jsonService() {
        JsonService jsonService = new JsonService();
        // 使用从配置文件中的读取到的配置。
        jsonService.setPrefixName(jsonProperties.getPrefix());
        jsonService.setSuffixName(jsonProperties.getSuffix());
        return jsonService;
    }
}

代码说明:

  • @EnableConfigurationProperties:将 @ConfigurationProperties 注解的类注册为 Spring Bean,并使其能够从外部配置中读取属性值。
  • @Configuration、@ConfigurationOnClass:前者表示这个配置类、需要在满足后一个注解的条件下才起作用、即需要满足在类路径下包含JsonService.class才装配当前类。
  • @ConditionalOnMissingBean(JsonService.class)、@ConditionalOnProperty(name = "super.json.enable",matchIfMissing = true)@ConditionalOnClass类似、只有满足了条件才会装配这个Bean

常用的条件化注解配置如下:

注解说明配置属性
@ConditionalOnProperty根据配置文件中的属性值来决定是否加载配置。name: 指定要检查的属性名称。 havingValue: 指定属性值,只有属性值匹配此值时,条件才会成立。 matchIfMissing: 指定如果属性缺失时是否匹配
@ConditionalOnClass当类路径中存在指定的类时,加载配置value: 指定类路径中的类或类的数组。
@ConditionalOnMissingBean当容器中没有指定类型的 Bean 时,加载配置value: 指定 Bean 的类型或类型的数组
@ConditionalOnBean容器中存在指定类型的 Bean 时,加载配置value: 指定 Bean 的类型或类型的数组。
@ConditionalOnMissingClass当类路径中不存在指定的类时,加载配置value: 指定类路径中的类或类的数组

到这里基本功能就已经完成了。

装配到Application中

在我们使用 spring boot 整合redis这些starter的过程中。引入相应的starter依赖就可以注入RedisTemplate<String, Object> redisTemplate;供我们使用。接下来我们就要完成这部分工作。

使用@Import方式

首先什么不再进行编码上的工作、通过 ideamaven install 将其安装到本地的 maven 仓库。

maven_install.png

接着在需要使用的服务中引入此模块的坐标:

<dependency>
    <groupId>com.supersist</groupId>
    <artifactId>super-json-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

此时我们还不能正常使用、无法注入 jsonService、使用使用@Import注解、使用方式如下:

@SpringBootApplication
@Import({com.supersist.config.Configuration.class})
public class SpringRedisApplication implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(SpringRedisApplication.class);
    }
​
    @Autowired
    JsonService jsonService;
​
    @Override
    public void run(String... args) throws Exception {
        System.out.println(jsonService.objectToJson(" hello world "));
    }
​
    @Data
    static class Test{
           private String name;
           private Integer age;
    }
}

使用 @Import 引入之后就可以自动注入JsonService,运行结果如下:

使用jar.png

可以看到可以正常使用了,在 application.yml 添加相关配置

super:
  json:
    prefix: "@@json"
    suffix: "@@"

运行结果如下:可以看到功能上使用没什么问题。

配置.png

但是如果每一个stater都需要@Import这种方式引入的话、就不太好、因为我们本质上需要

spring.fatories

为了使我们引入即可用、则需要在resource目录下创建META-INF/spring.factories文件、添加如下的配置

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      com.supersist.config.Configuration

此时重新打包安装之后、重新加载依赖、即可直接使用、注释掉 @Import 相关代码得到如下运行结果

自动装配.png 可以看到运行效果前后一致、完美!

优化@Import使用方式

为了避免手动导入、可以自定义一个注解、通过注解来控制是否加载此json模块。自定义一个注解、代码如下:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(com.supersist.config.Configuration.class)
    public @interface EnableSuperJson {
    }

此时运行代码。也可以得到对应的运行结果。这里就不放图片了。最后向各位看官奉上思维导航图

自定义start思维导航图.png