Redis Utility Spring Boot Starter: Features, Usage, and Implementation Details
This article introduces a Spring Boot starter that provides a ready‑to‑use Redis utility suite—including common tools, distributed locks, message queues, delay queues, pagination queries and web helpers—explains how to add the Maven dependency, configure modules via YAML, and details the internal auto‑configuration and bean implementations for MQ and delayed messaging.
Redis has become an essential tool for internet companies, and many projects repeatedly implement similar functionalities. To simplify development, a Redis‑based utility suite is presented, aiming to be plug‑and‑play with out‑of‑the‑box features.
Implemented modules include:
common – shared utilities such as AOP tools;
delay – Redis‑based delayed queue;
lock – distributed lock implementation;
mq – message queue;
query – pagination and fuzzy query;
web – web‑related helpers (e.g., duplicate submission prevention);
Additional modules for social features, rate limiting, and idempotency are planned.
How to use :
Add the Maven dependency (or publish the artifact to a private/local repository for now):
<dependency>
<groupId>cn.org.wangchangjiu</groupId>
<artifactId>redis-util-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>Enable the desired modules in application.yaml :
redis:
util:
mq:
enable: true
delay:
enable: trueImplement a message sender (MQ or delayed message) as shown in the accompanying screenshots.
Implement a message listener for both MQ and delayed messages (screenshots provided).
MQ and delay implementation details
During container startup, Spring Boot auto‑configuration creates several beans. With Spring Boot 3.x the auto‑configuration file META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports must list the configuration classes, such as RedisUtilAutoConfiguration , which imports module‑specific auto‑configuration classes.
The key beans are:
RedisMessageConsumerManager – implements BeanPostProcessor to detect methods annotated with @RedisMessageListener and wrap them in RedisMessageConsumerContainer for later reflective invocation.
StreamMessageListenerContainer – configures Redis MQ parameters (batch size, poll timeout, executor, error handler, consumer groups, ACK mode, etc.).
The bean definition for the listener container is:
@Bean(initMethod = "start", destroyMethod = "stop")
@DependsOn("redisMessageConsumerManager")
@ConditionalOnMissingBean
public StreamMessageListenerContainer
> streamMessageListenerContainer(
@Autowired RedisMessageConsumerManager redisMessageConsumerManager,
@Autowired RedisConnectionFactory redisConnectionFactory,
@Autowired ErrorHandler errorHandler) {
MyRedisStreamProperties.Options options = myRedisStreamProperties.getOptions();
StreamMessageListenerContainer.StreamMessageListenerContainerOptions
> containerOptions =
StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()
.batchSize(options.getBatchSize())
.executor(getStreamMessageListenerExecutor())
.pollTimeout(options.getPollTimeout())
.errorHandler(errorHandler)
.build();
StreamMessageListenerContainer
> container =
StreamMessageListenerContainer.create(redisConnectionFactory, containerOptions);
Map
consumerContainerGroups =
redisMessageConsumerManager.getConsumerContainerGroups();
consumerContainerGroups.forEach((groupQueue, redisMessageConsumerContainer) -> {
String[] groupQueues = groupQueue.split("#");
createGroups(groupQueues);
RedisMessageListener redisMessageListener = redisMessageConsumerContainer.getRedisMessageListener();
if (!redisMessageListener.useGroup()) {
container.receive(StreamOffset.fromStart(groupQueues[1]), new DefaultGroupStreamListener(redisMessageConsumerContainer));
} else {
if (redisMessageListener.autoAck()) {
container.receiveAutoAck(Consumer.from(groupQueues[0], "consumer:" + UUID.randomUUID()),
StreamOffset.create(groupQueues[1], ReadOffset.lastConsumed()), new DefaultGroupStreamListener(redisMessageConsumerContainer));
} else {
container.receive(Consumer.from(groupQueues[0], "consumer:" + UUID.randomUUID()),
StreamOffset.create(groupQueues[1], ReadOffset.lastConsumed()), new DefaultGroupStreamListener(redisMessageConsumerContainer));
}
}
});
return container;
}
/**
* Create consumer group
*/
private void createGroups(String[] groupQueues) {
if (stringRedisTemplate.hasKey(groupQueues[1])) {
StreamInfo.XInfoGroups groups = stringRedisTemplate.opsForStream().groups(groupQueues[1]);
if (groups.isEmpty()) {
stringRedisTemplate.opsForStream().createGroup(groupQueues[1], groupQueues[0]);
} else {
AtomicBoolean exists = new AtomicBoolean(false);
groups.forEach(xInfoGroup -> {
if (xInfoGroup.groupName().equals(groupQueues[0])) {
exists.set(true);
}
});
if (!exists.get()) {
stringRedisTemplate.opsForStream().createGroup(groupQueues[1], groupQueues[0]);
}
}
} else {
stringRedisTemplate.opsForStream().createGroup(groupQueues[1], groupQueues[0]);
}
}
// Thread pool for the listener container
private Executor getStreamMessageListenerExecutor() {
AtomicInteger index = new AtomicInteger(1);
int processors = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(processors, processors, 0, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(), r -> {
Thread thread = new Thread(r);
thread.setName("async-stream-consumer-" + index.getAndIncrement());
thread.setDaemon(true);
return thread;
});
return executor;
}The message sending flow is illustrated with diagrams (included in the original article). The delayed‑queue implementation follows a similar pattern, leveraging Redisson's delayed queue, custom annotations, and reflection.
For further reading, see the original Juejin post https://juejin.cn/post/7144969196542099469 and the source repository https://gitee.com/listen_w/redis-util-spring-boot-starter .
Note: The article also contains promotional material for a backend‑focused technical community; this part is not part of the technical content.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.