# 自定义 starter

<span alt='solid'> 需求 </span>:

自定义 redis-starter。要求当导入 redis 坐标时,SpringBoot 自定创建 Jedis 的 Bean。

我们知道 SpringBoot 提供了很多很多的 starter 起步依赖,但是有些起步依赖并没有提供。而是由某个技术自己写的它希望和 SpringBoot 整合它自己写的 starter 起步依赖。

比如说 Mybatis 就是这样做的,自己写的起步依赖让 SpringBoot 整合一下,接下来可以参考 Mybatis 的做法找到对应的思路。

<span alt='solid'> 在 pom.xml 中引入 Mybatis 的起步依赖 </span>:

<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>1.3.2</version>
</dependency>

一般 SpringBoot 官方提供的起步依赖功能名写在最后面比如 test,而一般第三方提供的起步依赖功能名写在最前面比如 mybatis

image-20230913150659200

mybatis 起步依赖里面包含的坐标点击进去查看

比如其中的坐标如下:

<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>

从其名得其意 , 这个坐标哦就是 mybatis 自动配置的一个坐标。当我们引入到 mybatis 的 starter 起步依赖的坐标后,自动配置和其它的依赖就能加载得到了

在 mybatis 的起步依赖中其实有什么功能代码也没有,只是将起步依赖中的所有坐标进行了整合

image-20230913151926826

我们关键看 mybatis-spring-boot-autoconfigure 依赖,其中的代码就比较多了。

image-20230913152049774

比如说我们可以看下 MybatisAutoConfiguration 这个类,这个类就是 Mybatis 的自动配置类

image-20230913152330440

那么这个配置类要能够被 SpringBoot 所识别从而加载这个 配置类 里面定义的 Bean 的话,那么它的做法就是在 META-INF 中定义了一个 spring.factories 的文件

image-20230913152640934

这样就能够在启动 SpringBoot 的时候读取到这个配置文件,从而加载 Mybatis 相关的 Bean 到 IOC 容器中进行使用。

# 实现步骤

  1. 创建 redis-spring-boot-autoconfigure 模块
  2. 创建 redis-spring-boot-starter 模块,依赖 redis-spring-boot-autoconfigure 的模块
  3. 在 redis-spring-boot-autoconfigure 模块中初始花 Jedis 的 Bean,并定义 META-INF/spring.factories 文件
  4. 创建 redis-spring-enable 模块是 redis-spring-boot-autoconfigure 的子模块

模块结构:

image-20230913170937301

子模块的目录结构:

image-20230913171044858

<span alt='solid'>RedisProperties 类 </span>.

通过注解 ConfigurationProperties 读取以 redis 前缀命名的配置文件的内容映射到字段上,并且如果没有配置信息则会有默认值就是本机

import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "redis")
public class RedisProperties {
    // 主机名
    private String host = "localhost";
    // 端口号
    private int port = 6379;
    public String getHost() {
        return host;
    }
    public void setHost(String host) {
        this.host = host;
    }
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }
}

<span alt='solid'>RedisAutoConfiguration 类 </span>.

使用注解:EnableConfigurationProperties 将指定的 RedisProperties 读取配置文件类注册到 IOC 容器当中,ConditionalOnClass 判断是否有 Jedis 的依赖再加载类中定义的 Bean,注解:ConditionalOnMissingBean (name = "jedis") 判断用户是否定义了名叫 jedis 的 Bean 如果没有则使用我们定义的,如果有则使用用户定义的。

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
// 如果有 Jedis 的再加载 Bean
@ConditionalOnClass(Jedis.class)
public class RedisAutoConfiguration {
    /**
     * 提供 jedis 的 Bean
     */
    // 如果用户自己定义了 jedis 的 Bean 则使用用户定义的
    // ConditionalOnMissingBean 如果没有名称叫 jedis 的 Bean 的时候再提供我们的 jedis 的 Bean
    @Bean
    @ConditionalOnMissingBean(name = "jedis")
    public Jedis jedis (RedisProperties redisProperties) {
        System.out.println("RedisAutoConfiguration ... ");
        return new Jedis(redisProperties.getHost(), redisProperties.getPort());
    }
}

找到 SpringBoot 启动时会自动读取配置文件来加载 Bean 的位置 @EnableAutoConfiguration

image-20230913171908801

进入到 AutoConfigurationImportSelector 中

image-20230913171837565

复制这段

image-20230913171947375

在 redis-spring-boot-starter 模块的 resources 下创建 META-INF /spring.factories 配置文件,这个配置文件就是 SpringBoot 启动时就会读取的然后加载里面配置的依赖项

spring.factories 内容:

其中的 \ 表示,这段代码太长了 换下行而已

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.dkx.config.RedisAutoConfiguration

SpringBoot 启动类

位置:redis-spring-enable \ src \ main \ java \ com \ dkx \ RedisSpringEnableApplication

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import redis.clients.jedis.Jedis;
@SpringBootApplication
public class RedisSpringEnableApplication {
	public static void main(String[] args) {
		ConfigurableApplicationContext run = SpringApplication.run(RedisSpringEnableApplication.class, args);
		Jedis jedis = run.getBean(Jedis.class);
		jedis.set("username", "liusang");
		String value = jedis.get("username");
		System.out.println(value);
	}
}

运行结果:

image-20230913172626902

如果用户自己定义了 jedis 这个 Bean 的话

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import redis.clients.jedis.Jedis;
@SpringBootApplication
public class RedisSpringEnableApplication {
	public static void main(String[] args) {
		ConfigurableApplicationContext run = SpringApplication.run(RedisSpringEnableApplication.class, args);
		Jedis jedis = run.getBean(Jedis.class);
		jedis.set("username", "liusang");
		String value = jedis.get("username");
		System.out.println(value);
	}
	// 重写 jedis 的 Bean 如果我们自定义的没有打印内容说明注解:ConditionalOnMissingBean 生效
	@Bean
	public Jedis jedis() {
		return new Jedis("localhost", 6379);
	}
}

运行结果:

image-20230913172724154

而且我们也可以自己来改 yml 中的配置比如改一下端口号,但是注意:将用户定义的 jedis 注释掉因为会使用它的就不会使用我们自己定义的了所以配置文件就等于白配置

redis:
  port: 666

运行结果:

image-20230913173001307

报错信息是端口号不对