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.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Implementing a Distributed Redis Lock with Spring Boot AOP and Automatic Renewal

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaaopSpring Boot
Code Ape Tech Column
Written by

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

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.