如何自己实现一个springbootstarter?

玩科技的勇哥鸭 2024-03-14 02:19:15

我写了一个 Redis Java SDK 客户端,为了简化 springboot 项目配置, 自己实现了一个 springboot starter 。

Github 地址 :https://github.com/makemyownlife/platform-redis

1 SDK 设计思路

SDK 的设计理念核心两点:

提供精简的 API 供开发者使用,方便与用户接入;屏蔽三方依赖,用户只和对外 API 层交互 。

Redis SDK 如上图,分为三层:

最上层为 API ,用户与该层打交道 ,为了方便用户方便接入,设计了springboot starter模块。中间为核心功能层,提供了两类功能:Redis 操作命令(比如 String 相关命令)和增强功能(比如分布式锁)。最底层是三方依赖,我们有两种选择:自己实现与 Redis 服务端的交互,工作量非常大,另一种是选择选择成熟的 Redis Java 客户端框架 ,再此基础上进行封装,并补充其他的实现。笔者选择 Redisson 框架,因为该框架实现了非常高级的功能,比如分布式锁,延迟队列等。2 操作命令封装

封装操作命令时基本原则是:尽量不要提供危险的接口供开封者使用,尽量减少开封者的使用心智。

比如,KEYS 命令用于查找符合指定模式的所有键。它是一个用于调试目的的命令,但在生产环境中不推荐频繁使用,因为在大型数据库中执行 KEYS 命令可能会导致性能问题。考虑到假如提供给开发者使用会引发,我们并不会封装 KEYS 命令给开发者使用。

项目分为四个模块:

客户端模块:核心模块,封装了 Redis 操作命令(比如 String 相关命令)和增强功能(比如分布式锁)。springboot starter : 开发者只需要在 pom.xml 引入 starter 的依赖定义,在配置文件中编写约定的配置。ID 生成器: 通过 Redis 改造雪花算法生成唯一编号。使用示例:使用简单的 springboot starter 的例子。

本节我们重点讲解客户端模块的实现。

//1. 定义配置 SingleConfig config = new SingleConfig();config.setAddress("redis://127.0.0.1:6379");//2. 定义Redis操作对象RedisOperation redisOperation = new RedisOperation(config);// 3.使用String命令的 setEx 方法 StringCommand stringCommand = redisOperation.getStringCommand();stringCommand.setEx("hello", "mylife", 109);// 4.使用Hash命令的hset方法 HashCommand hashCommand = this.redisOperation.getHashCommand();hashCommand.hset("myhash", "time", "mybatis");

首先我们定义一个配置类,例子中是单机配置类 SingleConfig ,还有集群配置、主从配置。

然后初始化操作对象 RedisOperation , 参数是配置对象。最后获取内置的 String 命令 、Hash 命令对象,调用该对象的操作方法。

RedisOperation 对象内置了 RedissonClient 对象 ,该对象对用户是不可视的。

public RedisOperation(SingleConfig SingleServerConfig) { Config config = ConfigBuilder.buildBySingleServerConfig(SingleServerConfig); //默认string编解码 config.setCodec(new StringCodec()); this.redissonClient = Redisson.create(config);}

三种根据配置类初始化的构造函数,每个构造函数内会创建初始化 RedissonClient 对象。

然后根据 RedissonClient 对象创建不同的操作命令:

public StringCommand getStringCommand() { StringCommand StringCommand = new StringCommandImpl(this.redissonClient); return StringCommand;}public StringCommand getStringCommand(RedisCodec RedisCodec) { StringCommand StringCommand = new StringCommandImpl(this.redissonClient , RedisCodec); return StringCommand;}public AtomicCommand getAtomicCommand() { AtomicCommand AtomicCommand = new AtomicCommandImpl(this.redissonClient); return AtomicCommand;}public ListCommand getListCommand() { ListCommand ListCommand = new ListCommandImpl(this.redissonClient); return ListCommand;}// 省略其他的命令代码

最后,我们看看字符串操作命令 StringComand 如何实现。

1、核心接口

public interface StringCommand extends KeyCommand { String get(String key);​ void set(String key, String value);​ void setEx(String key, String value, int second);​ boolean setNx(String key, String value);​ boolean setNx(String key, String value, int aliveSecond);​ Map<String, Object> mget(String... keys);​}

2、接口实现类

public StringCommandImpl extends KeyCommandImpl implements StringCommand { public StringCommandImpl(RedissonClient redissonClient) { super(redissonClient); } public StringCommandImpl(RedissonClient redissonClient, RedisCodec redisCodec) { super(redissonClient, redisCodec); } public String get(final String key) { return invokeCommand(new InvokeCommand<String>(RedisCommandType.GET) { @Override public String exe(RedissonClient redissonClient) { RBucket<String> result = getRedissonClient().getBucket(key); if (result == null || !result.isExists()) { return null; } return result.get(); } }); } public void set(final String key, final String value) { invokeCommand(new InvokeCommand<String>(RedisCommandType.SET) { @Override public String exe(RedissonClient redissonClient) { RBucket<String> result = getRedissonClient().getBucket(key); result.set(value); return null; } }); } public void setEx(final String key, final String value, final int second) { invokeCommand(new InvokeCommand<String>(RedisCommandType.SET_EX) { @Override public String exe(RedissonClient redissonClient) { RBucket<String> result = getRedissonClient().getBucket(key); result.set(value, second, TimeUnit.SECONDS); return null; } }); } public boolean setNx(final String key, final String value) { return invokeCommand(new InvokeCommand<Boolean>(RedisCommandType.SET_NX) { @Override public Boolean exe(RedissonClient redissonClient) { RBucket<String> result = getRedissonClient().getBucket(key); return result.trySet(value); } }); } public boolean setNx(final String key, final String value, final int aliveSecond) { return invokeCommand(new InvokeCommand<Boolean>(RedisCommandType.SET_NX) { @Override public Boolean exe(RedissonClient redissonClient) { RBucket<String> result = getRedissonClient().getBucket(key); return result.trySet(value, aliveSecond, TimeUnit.SECONDS); } }); } public Map<String, Object> mget(final String... keys) { return invokeCommand(new InvokeCommand<Map<String, Object>>(RedisCommandType.MGET) { @Override public Map<String, Object> exe(RedissonClient redissonClient) { return getRedissonClient().getBuckets().get(keys); } }); } }

实现类里面方法实现都比较简单,都是使用 RedissonClient 的 API 方法 ,我们做一层简单的包装。

在包装类内部,我们除了实现基本的 API 调用之外,也可以做访问统计等额外功能。

3 实现 springboot starter3.1 启动器

我们都知道,Spring Boot 基于“约定大于配置”(Convention over configuration)这一理念来快速地开发、测试、运行和部署 Spring 应用,并能通过简单地与各种启动器(如 spring-boot-web-starter)结合,让应用直接以命令行的方式运行,不需再部署到独立容器中。

Spring Boot starter 构造的启动器使用起来非常方便,开发者只需要在 pom.xml 引入 starter 的依赖定义,在配置文件中编写约定的配置即可。

很多开源组件都会为 Spring 的用户提供一个 spring-boot-starter 封装给开发者,让开发者非常方便集成和使用。

spring-boot-starter 实现流程如下:

01、定创建starter项目,定义 Spring 自身的依赖包和 Bean 的依赖包 ;

02、定义spring.factories 文件

在 resources 包下创建 META-INF 目录后,新建 spring.factories 文件,并在文件中定义自动加载类,文件内容格式:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\xx.xx.xx.xx.xx.MyConfig

spring boot 会根据文件中配置的自动化配置类来自动初始化相关的 Bean、Component 或 Service。

03、配置自动配置类

编写自动配置类,这些类将在Spring应用程序中自动配置starter。自动配置类应该有一个@Configuration注解,并且应该包含可以覆盖的默认值,以允许用户自定义配置。在自动配置类中,可以使用@ConditionalOnClass、@ConditionalOnMissingBean等条件注解,以便只有在需要的情况下才会配置 starter。

3.2 实现方式

首先在 resources 包下创建 META-INF 目录后,新建 spring.factories 文件,并在文件中定义自动加载类,文件内容格式:

# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.courage.platform.redis.client.springboot.starter.configuration.RedisClientAutoConfiguration

然后定义自动配置类 RedisClientAutoConfiguration。

@Configurationpublic RedisClientAutoConfiguration { @Configuration @ConditionalOnMissingBean(Config.class) @ConditionalOnProperty(name = "spring.redis.type", havingValue = "single") static StaticBuildSingleServer { @Bean(value = "platformSingleServerConfig") @ConfigurationProperties(prefix = "spring.redis.single") public SingleConfig getSingleConfig() { SingleConfig config = new SingleConfig(); return config; } @Bean public Config singleServerConfig(SingleConfig singleConfig) { return ConfigBuilder.buildBySingleServerConfig(singleConfig); } @Bean(destroyMethod = "shutdown") public RedisOperation redisOperation(Config config) { RedisOperation redisOperation = new RedisOperation(config); return redisOperation; } } @Configuration @ConditionalOnMissingBean(Config.class) @ConditionalOnProperty(name = "spring.redis.type", havingValue = "sentinel") static StaticBuildSentinelServer { @Bean(value = "platformSentinelServerConfig") @ConfigurationProperties(prefix = "spring.redis.sentinel") public SentinelConfig getPlatformSentinelServersConfig() { SentinelConfig sentinelConfig = new SentinelConfig(); return sentinelConfig; } @Bean public Config sentinelServerConfig(SentinelConfig sentinelConfig) { return ConfigBuilder.buildBySentinelServerConfig(sentinelConfig); } @Bean(destroyMethod = "shutdown") public RedisOperation redisOperation(Config config) { RedisOperation redisOperation = new RedisOperation(config); return redisOperation; } } @Bean("stringCommand") @ConditionalOnBean(RedisOperation.class) public StringCommand createStringCommand(RedisOperation redisOperation) { return redisOperation.getStringCommand(); } @Bean @ConditionalOnBean(RedisOperation.class) public ZSetCommand createZSetCommand(RedisOperation redisOperation) { return redisOperation.getZSetCommand(); } @Bean @ConditionalOnBean(RedisOperation.class) public SetCommand createPlatformSetCommand(RedisOperation redisOperation) { return redisOperation.getSetCommand(); } @Bean @ConditionalOnBean(RedisOperation.class) public AtomicCommand createPlatformAtomicCommand(RedisOperation redisOperation) { return redisOperation.getAtomicCommand(); } @Bean @ConditionalOnBean(RedisOperation.class) public HashCommand createHashCommand(RedisOperation redisOperation) { return redisOperation.getHashCommand(); } @Bean @ConditionalOnBean(RedisOperation.class) public ScriptCommand createPlatformScriptCommand(RedisOperation redisOperation) { return redisOperation.getScriptCommand(); } @Bean @Primary @ConditionalOnBean(RedisOperation.class) public IdGenerator createIdGenerator(RedisOperation redisOperation) { return new IdGenerator(redisOperation); } }

该配置类首先会根据配置类创建 RedisOperation 对象 ,然后获取命令对象(比如 StringCommand、HashCommand)注入到 Spring 容器里。

3.3 如何使用

1、pom文件添加依赖

<dependency> <groupId>com.courage</groupId> <artifactId>platform-redis-client-springboot-starter</artifactId> <version>1.0.0-SNAPSHOT</version></dependency>

2、yaml缓存配置

spring: redis: type: single single: address: 127.0.0.1:6379

3、使用缓存

@Autowiredprivate RedisOperation redisOperation;@RequestMapping(value = "/hello", method = RequestMethod.GET)@ResponseBodypublic String hellodanbiao() { String mylife = redisOperation.getStringCommand().get("hello"); System.out.println(mylife); redisOperation.getStringCommand().set("hello", "zhang勇"); return "hello-service";}

参考资料:

Redis 集成简介:https://juejin.cn/post/70762445

0 阅读:1

玩科技的勇哥鸭

简介:感谢大家的关注