Implementing a Distributed Redis Lock with Spring Boot AOP and Automatic Renewal
This article explains how to protect time‑consuming business operations using a Redis‑based distributed lock in a Spring Boot application, covering annotation design, AOP pointcuts, lock acquisition and release, timeout handling, automatic renewal via a ScheduledExecutorService, and practical testing results.
Business Background
Some business requests involve time‑consuming operations that require locking to prevent concurrent modifications and ensure data integrity in the database.
Analysis Process
Redis is used as a distributed lock, storing lock state in Redis to synchronize JVMs in a cluster and enforce operation order.
Design Steps
Create a custom annotation @interface to define input parameters.
Add an AOP pointcut to scan the annotation.
Define an @Aspect class to register a bean and intercept the target method.
Use ProceedingJoinPoint to intercept before and after pjp.proceed().
Lock before the pointcut and delete the key after task execution.
Core Steps: Lock, Unlock, and Renewal
Locking
Use RedisTemplate.opsForValue().setIfAbsent to set a unique UUID as the lock value; if successful, set an expiration time to automatically release the lock.
Timeout Issue
If the intercepted method takes longer than the lock timeout, the lock may be released prematurely, causing concurrent threads to acquire the same lock and corrupt data.
Solution – Renewal
A ScheduledExecutorService maintains a task queue and periodically extends the lock expiration before it expires.
/**
* Thread pool, each JVM uses one thread to maintain keyAliveTime, scheduled execution
*/
private static final ScheduledExecutorService SCHEDULER =
new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("redisLock-schedule-pool").daemon(true).build());
static {
SCHEDULER.scheduleAtFixedRate(() -> {
// iterate over holderList, extend lock if needed
}, 0, 2, TimeUnit.SECONDS);
}Design Solution
The final design includes intercepting the @RedisLockAnnotation, acquiring the lock, adding the task to a renewal queue, handling thread interruption after retry limits, and releasing the lock in a finally block.
Aspect Implementation
@Pointcut("@annotation(cn.sevenyuan.demo.aop.lock.RedisLockAnnotation)")
public void redisLockPC() {}
@Around("redisLockPC()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Method method = resolveMethod(pjp);
RedisLockAnnotation annotation = method.getAnnotation(RedisLockAnnotation.class);
String businessKey = annotation.typeEnum().getUniqueKey(pjp.getArgs()[annotation.lockFiled()].toString());
String uniqueValue = UUID.randomUUID().toString();
Object result = null;
try {
boolean isSuccess = redisTemplate.opsForValue().setIfAbsent(businessKey, uniqueValue);
if (!isSuccess) {
throw new Exception("You can't do it, because another has got the lock");
}
redisTemplate.expire(businessKey, annotation.lockTime(), TimeUnit.SECONDS);
// add to renewal queue
holderList.add(new RedisLockDefinitionHolder(businessKey, annotation.lockTime(),
System.currentTimeMillis(), Thread.currentThread(), annotation.tryCount()));
result = pjp.proceed();
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException("You had been interrupted");
}
} catch (InterruptedException e) {
log.error("Interrupt exception, rollback transaction", e);
throw new Exception("Interrupt exception, please send request again");
} catch (Exception e) {
log.error("has some error, please check again", e);
} finally {
redisTemplate.delete(businessKey);
log.info("release the lock, businessKey is [" + businessKey + "]");
}
return result;
}Testing
A test endpoint uses the annotation with @GetMapping("/testRedisLock") and simulates a long‑running task with Thread.sleep. Logs show lock acquisition, renewal attempts, and interruption when the retry limit is exceeded.
@GetMapping("/testRedisLock")
@RedisLockAnnotation(typeEnum = RedisLockTypeEnum.ONE, lockTime = 3)
public Book testRedisLock(@RequestParam("userId") Long userId) {
try {
log.info("Before sleep");
Thread.sleep(10000);
log.info("After sleep");
} catch (Exception e) {
log.info("has some error", e);
}
return null;
}Conclusion
For time‑consuming business logic, a distributed Redis lock combined with AOP and a renewal mechanism prevents duplicate concurrent operations and ensures data correctness. The article also reviews AOP basics, scheduled thread pools, and thread interruption handling.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
