自定义SpringBoot Starter组件

young 489 2021-10-31

SpringBoot 自动装配

@SpringBootApplication注解中可以看到自动装配注解@EnableAutoConfiguration

@EnableAutoConfiguration中可以看到@Import(AutoConfigurationImportSelector.class)

AutoConfiguration.png

AutoConfigurationImportSelectorselectImports方法中,调用了getAutoConfigurationEntry方法

selectImports.png

该方法会拿到需要自动装配的类名和需要排除自动装配的类名

getAutoConfigurationEntry.png

查看getCandidateConfigurations方法,该方法的描述为返回自动装配的类名

getCondidateConfigurations.png

查看SpringFactoriesLoader.loadFactoryNames方法

loadFactoryNames.png

可以看到该方法从META-INF/spring.factories获取全限定类名

查看sping-boot-autoconfigure下的spring.factories文件

springbootautoconfigurespring.factories.png

可以看到许多spring内置的自动装配类。

查看mybatis-spring-boot-autoconfigure中的spring.factories

mybatisspringbootautoconfigurespring.factories.png

由此可见,spring的自动装配是通过读取META-INF\spring.factories,获取自动装配类的全限定名称。

AutoConfigurationImportSelector类实现了DeferredImportSelector接口,DeferredImportSelector继承了ImportSelector接口,所以AutoConfigurationImportSelector类本质上是一个ImportSelector。

ImportSelector的描述是确定那些类可以按照@Configuration的方式处理,即将指定的类注入到Spring容器中。

ImportSelector在ConfigurationClassParser的processImports方法中被使用。

在doProcessConfigurationClass方法中,可以看到@Component,@PropertySources,@ComponentScans,@Import,@ImportResource,@Bean 注解修饰的类都会被处理,放入Map<ConfigurationClass, ConfigurationClass> configurationClasses中。

在ConfigurationClassPostProcessor中类中会调用该配置,该类实现了BeanDefinitionRegistryPostProcessor接口,方法postProcessBeanDefinitionRegistry用来注册更多的bean到spring容器中,在这期间,会获取configurationClasses中的配置,进行bean是否需要进行注册判断,@Conditional相关注解的判断也是在此方法中进行的,@ConditionalOnBean与@ConditionalOnMiss注解不生效的原因也与此处BeanDefinition的注册顺序有关

条件注解 @ConditionalOnBean 的正确使用姿势

自定义Redisson Starter

新建一个maven queck-start 项目

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.5.6</version>
    <!-- 依赖不传递 -->
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.4</version>
    <!-- 依赖不传递 -->
    <optional>true</optional>
</dependency>

创建配置类

@ConfigurationProperties(prefix = "my.redisson")
public class RedissonProperties {
    private String host = "localhost";
    
    private int port = 6379;

    private String password;

    private int timeout;

    private boolean ssl;

    // getter and setter
}

创建自动装配类

// 条件装配,存在Redisson类时才装配
@ConditionalOnClass(Redisson.class)
// 将RedissProperties注入Spring
@EnableConfigurationProperties(RedissonProperties.class)
@Configuration
public class RedissonAutoConfiguration {

   @Bean
   public RedissonClient redissonClient(RedissonProperties redissonProperties) {
      Config config = new Config();
      String prefix = "redis://";
      if (redissonProperties.isSsl()) {
         prefix = "rediss://";
      }
      config.useSingleServer().setAddress(prefix + redissonProperties.getHost() + ":" + redissonProperties.getPort())
            .setPassword(redissonProperties.getPassword()).setTimeout(redissonProperties.getTimeout());
      return Redisson.create(config);
   }
}

创建spring.factories

在resources下创建目录META-INF,并在该目录下创建文件spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.example.RedissonAutoConfiguration

在IDEA中,拼写正确文件前会有一个spring的标识

执行 mvn install -Dmaven.test.skip=true 进行依赖打包

测试是否有自动装配

通过start.spring.io创建一个SpringBoot项目,引入之前打包的依赖

<dependency>
    <groupId>org.example</groupId>
    <artifactId>my-redisson-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

在application.yaml中添加自定义的配置

my:
  redisson:
    host: xxxxx
    port: 6379
    password: xxxx
    timeout: 5000

将main方法改为

public static void main(String[] args) {
       ConfigurableApplicationContext applicationContext = SpringApplication.run(TestRedissonApplication.class, args);
       System.out.println(applicationContext.getBean(RedissonAutoConfiguration.class));
   }

此时启动项目,控制台会提示没有找到相关的bean,因为之前的pom中,Redisson依赖的option为true,所以需要手动将Redisson引入,这么做只是为了演示自动装配是否生效,实际使用中,自定义的start项目中redisson的依赖应没有option项

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.4</version>
</dependency>

引入之后再启动项目,此时可以发现,控制台中已经打印出了我们自定义的Starter实现类地址,说明自动装配成功了,一个自定义的start组件完成了。

增加配置文件提示

目前为止,我们自定义的starter还无法实现在配置文件中提示,这对使用者来说十分不友好。

回到我们的自定义starter项目,增加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <version>2.5.6</version>
    <optional>true</optional>
</dependency>

执行 mvn clean install -Dmaven.test.skip=true,重新进行打包

打包结束之后,在target/classes/META-INF下,可以看见生成了spring-configuration-metadata.json文件,里面就是配置类相关的配置生成,此时刷新测试项目的依赖,再去application.yaml输入配置,即可出现相关的提示。

如果想要给提示增加描述信息,需在要starter的项目中,resource/META-INF下创建additional-spring-configuration-metadata.json,然后进行编辑

在IDEA中,拼写正确文件前会有一个spring的标识

{
  "properties": [
    {
      "name": "my.redisson.host",
      "type": "java.lang.String",
      "sourceType": "org.example.RedissonProperties",
      "defaultValue": "localhost",
      "description": "redis的连接地址"
    },{
      "name": "my.redisson.port",
      "type": "java.lang.Integer",
      "sourceType": "org.example.RedissonProperties",
      "defaultValue": 6379,
      "description": "redis的端口"
    }
  ]
}

之后再次打包,可以发现spring-configuration-metadata.json中增加了相关属性

此时再去更新测试项目依赖,即可在配置配置文件时看到对应的描述