外观
后端与配置
应用通常注入默认字符串模板读写数据,在需要隔离编码协议时通过 StoreClient 创建额外模板。后端由依赖和 own.store.backend 共同决定,不会因为类路径中出现 Redisson 就自动改用 Redis。
本地后端
使用快速开始中的 springboot-component-store-starter-local。数据保存在当前客户端的内存中,适合单实例临时数据、开发演示和不需要跨实例协调的场景。
每个命名空间模板为 Value、Map、Set、List、Atomic 分别创建有容量限制的数据区。maximum-size 是每个数据区的键数上限,不是整个应用的总键数,也不是字节数。Caffeine 可淘汰已有键;即使未设置 TTL,数据仍可能丢失。
单个 Map 的字段数、Set 的成员数或 List 的元素数超过 maximum-collection-size 时抛出 StoreLimitException。不同命名空间会增加独立的数据区,客户端还会缓存模板,因此应使用有限、稳定的命名空间,把用户 ID 等高基数字段放在逻辑键中。
Redis 后端
沿用快速开始中的版本管理,将运行时依赖替换为:
xml
<dependency>
<groupId>com.own.component</groupId>
<artifactId>springboot-component-store-starter-redis</artifactId>
</dependency>该 Starter 同时引入 Store Redisson 实现和 redisson-spring-boot-starter。连接已有 Redis 的最小配置如下:
yaml
own:
store:
backend: redis
namespace: example-service
spring:
data:
redis:
host: 127.0.0.1
port: 6379
database: 0按部署环境补充连接认证、TLS、哨兵或集群配置。需要 Redisson 原生配置时使用 spring.redis.redisson.file 或 spring.redis.redisson.config,不属于 own.store 属性。已有自定义 RedissonClient Bean 时可以复用;Store 不修改该客户端的全局 Codec。
选择 redis 后必须存在可唯一解析的 RedissonClient,缺少时启动失败,不会回退本地;多个客户端需通过 @Primary 明确选择。同样,只引入 Redis Starter 而不设置 backend: redis,也不能获得默认本地实现。
多实例共享数据、锁或限流时,要使用相同 Redis 目标、数据库和命名空间,并约定一致的逻辑键与编码协议。修改 namespace 会切换数据空间,不会迁移旧数据。
配置参考
下表属性均以 own.store. 为前缀:
| 属性 | 默认值 | 约束与作用 |
|---|---|---|
backend | local | 可选 local、redis;选择实现,不代替引入对应依赖 |
namespace | default | 非空,只允许英文字母、数字、点、下划线和连字符 |
scan-max-results | 1000 | 正整数,限制 keys 的物化结果;不限制 scanKeys 全量迭代 |
enable-data-cache | true | 控制自动创建的 DataCacheUtil 是否读写缓存;关闭仍保留 Bean |
local.maximum-size | 10000 | 正 long,每个本地数据区的最大键数 |
local.maximum-collection-size | 10000 | 正整数,单个本地 Map、Set、List 的容量上限 |
local.lock-stripes | 1024 | 正整数,本地锁分片数;键碰撞时不同逻辑键也会串行 |
local.maximum-rate-limiters | 10000 | 正 long,本地限流器缓存最大数量,另有一小时空闲淘汰 |
本地限额不约束 Redis 容量;缓存开关也不会关闭 StoreTemplate 的直接读写、锁或限流。namespace 和扫描上限在公共装配时校验,本地选项在创建本地实现时校验,非法值会导致相应 Bean 创建失败。
命名空间与编码协议
StoreClient.template(namespace, codec) 返回模板。同一客户端中,同一命名空间首次绑定后,使用相同 Codec ID 会返回已有模板;使用不同 ID 抛出 StoreException。这项检查只在客户端实例内生效,不会自动校验其他应用进程。
默认模板使用 StoreCodecs.string(),即 UTF-8 字符串,协议 ID 为 string:utf-8:v1。以下配置创建另一个字符串模板,不覆盖默认模板:
java
package example.store;
import com.own.component.store.api.StoreClient;
import com.own.component.store.api.StoreTemplate;
import com.own.component.store.api.common.StoreCodecs;
import com.own.component.store.api.common.StoreNamespace;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class StoreTemplateConfiguration {
@Bean("lookupStoreTemplate")
StoreTemplate<String> lookupStoreTemplate(StoreClient client) {
return client.template(
StoreNamespace.of("lookup-v1"), StoreCodecs.string());
}
}使用时以 @Qualifier("lookupStoreTemplate") 注入。Redis 默认锁和限流管理器仍使用自动配置的命名空间;新增模板不会自动生成配套管理器。需要隔离的 Redis 锁或限流器时应手动装配对应实例。
自定义对象编码实现 StoreCodec<T> 的 id()、encode(T) 和 decode(byte[])。组件只内置字符串 Codec;普通对象查询可先使用JSON 缓存工具。自定义协议需满足:
- ID 稳定且非空,包含类型与协议版本;不兼容变更使用新命名空间,不能仅替换实现却沿用旧 ID。
- 编码返回非 null 字节数组,解码应能还原相同协议的数据;在实现中处理序列化错误并保留异常原因。
- 相同值产生稳定字节。条件删除、Set 去重及集合的按值查找/删除依赖编码字节,不只是 Java 对象的
equals。 - 不修改或泄露可变字节数组;StoreCodecs.copy 可用于防御性复制。
Redis 中 Store 管理的键使用 o:v2:<namespace>:<type>:<logical-key>,类型包含 value、map、set、list、atomic、lock、rate。调用 API 时只传逻辑键;同名逻辑键在不同类型中独立存在。组件不提供旧前缀数据的自动读取或迁移。
自动配置与覆盖
| Bean | 创建前提与覆盖方式 |
|---|---|
| StoreClient | 有选定后端的实现类;按类型缺失时创建 |
| StoreNamespace | 按类型缺失时从配置创建 |
storeTemplate | 存在 StoreClient 且没有同名 Bean;默认是 StoreTemplate<String> |
| StoreLockManager、StoreRateLimiterManager | 选定后端可用且对应类型缺失 |
| DataCacheUtil | 有锁管理器和名为 storeTemplate 的模板,且没有该类型 Bean |
| StoreLockReleaseStrategy | 有 Spring 事务相关类且未自定义时提供同步事务感知策略,否则内置管理器回退立即释放 |
| StoreLockAspect | 有 AOP、SpEL 相关类和锁管理器,且没有该类型 Bean |
Starter 通过自动配置模块传递引入 AspectJ Starter;Spring 事务依赖是可选的。组件不创建数据库事务管理器,也不会仅因调用锁 API 而启动事务。
自定义同名 storeTemplate 时,仍需满足依赖它的组件所要求的字符串类型。只替换 StoreClient 不会一并替换锁和限流实现,应保持这几项的共享范围一致。自定义 DataCacheUtil 时,缓存开关由创建它的代码决定。
非 Spring 环境与后端扩展
引入 springboot-component-store-caffeine 后,可以直接装配本地对象:
java
import com.own.component.store.api.cache.DataCacheUtil;
import com.own.component.store.api.common.StoreCodecs;
import com.own.component.store.api.common.StoreNamespace;
import com.own.component.store.caffeine.LocalStoreClient;
import com.own.component.store.caffeine.LocalStoreLockManager;
import com.own.component.store.caffeine.LocalStoreOptions;
import com.own.component.store.caffeine.LocalStoreRateLimiterManager;
// 放入初始化方法,应用内复用这些实例。
var options = LocalStoreOptions.defaults();
var client = new LocalStoreClient(options, 1000);
var store = client.template(StoreNamespace.of("standalone"), StoreCodecs.string());
var locks = new LocalStoreLockManager(options.lockStripes());
var limiters = new LocalStoreRateLimiterManager(options.maximumRateLimiters());
var cache = new DataCacheUtil(store, locks);Redis 对应构造方法为 new RedissonStoreClient(redissonClient, scanMaxResults)、new RedissonStoreLockManager(redissonClient, namespace) 和 new RedissonStoreRateLimiterManager(redissonClient, namespace),类型位于 com.own.component.store.redisson。调用方负责创建和关闭 RedissonClient。手动创建的锁管理器默认立即释放;如需其他释放时机,通过构造参数提供 StoreLockReleaseStrategy。
实现新后端时,提供 StoreClient、StoreTemplate 及各数据接口,另行装配匹配的锁和限流管理器。可继承 AbstractStoreClient 复用模板与 Codec ID 注册。尤其要实现原子的 ValueStore.compareAndDelete:缓存损坏恢复依赖它;接口默认抛出不支持异常,不会降级成先读后删。AtomicStore.compareAndSet 和键扫描也有需要覆盖的默认不支持方法,不能只实现基本读写便视为兼容所有上层能力。