Distributed lock is one of the most common scenarios in practical applications. Although Redisson supports distributed lock implementation, it is still not convenient to use. This paper introduces an elegant distributed lock static method encapsulation and annotation encapsulation implementation.

Static method encapsulation

To facilitate the use of distributed locks in business code, this can be done directly through static method calls. Combined with functional programming, the complete interface is defined as follows:

/** * Distributed lock execution, return the execution result **@paramKey Indicates the distributed lock identifier *@paramLeaseTimeMs Automatic release time *@paramExecutable unit *@returnResult */
public static Object lockExecute(String key, Long leaseTimeMs, Executable executable);

/** * Distributed lock execution, no result is returned **@paramKey Indicates the distributed lock identifier *@paramLeaseTimeMs Automatic release time *@paramRunnable Executable unit */
public static void lockRun(String key, Long leaseTimeMs, Runnable runnable);
Copy the code

LockExecute () returns the result of distributed lock and lock execution, but lockRun() does not.

The complete code is as follows:

/ * * *@authorChen tian Ming * /
@UtilityClass
@Slf4j
public class RLockUtils {


    /** * Distributed lock execution, return the execution result **@paramKey Indicates the distributed lock identifier *@paramLeaseTimeMs Automatic release time *@paramExecutable unit *@returnResult */
    @SneakyThrows
    public static Object lockExecute(String key, Long leaseTimeMs, Executable executable) {
        RLock lock = doLock(key, leaseTimeMs);
        try {
            return executable.execute();
        } finally{ doUnlock(key, leaseTimeMs, lock); }}/** * Distributed lock execution, no result is returned **@paramKey Indicates the distributed lock identifier *@paramLeaseTimeMs Automatic release time *@paramRunnable Executable unit */
    public static void lockRun(String key, Long leaseTimeMs, Runnable runnable) {
        RLock lock = doLock(key, leaseTimeMs);
        try {
            runnable.run();
        } finally{ doUnlock(key, leaseTimeMs, lock); }}private static void doUnlock(String key, Long leaseTimeMs, RLock lock) {
        if(lock ! =null) {
            try {
                lock.unlock();
            } catch (Exception e) {
                log.error("Distributed lock release failed! Does not affect service execution! key={}, leaseTimeMs={}", key, leaseTimeMs, e); }}}private static RLock doLock(String key, Long leaseTimeMs) {
        RLock lock = null;
        try {
            RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class);
            lock = redissonClient.getLock(key);
            lock.lock(leaseTimeMs, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.error("Distributed locking failed! Normal service execution is not affected! key={}, leaseTimeMs={}", key, leaseTimeMs, e);
        }
        returnlock; }}Copy the code

Annotation package

Annotated programming is a more common approach in daily work. The following focuses on the implementation of annotated programming.

Custom annotation@RLock

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RLock {

    / * * * distributed lock logo, supports the following format: ABC * * # {param} * # {. User id} * abc_ # # {param} _ {. User id} *@return key
     */
    String key(a);

    /** * Automatic lock release time, in milliseconds, default 5_000 *@return leaseTime
     */
    long leaseTimeMs(a) default5 _000;
}
Copy the code

Define the aspect implementation

@Aspect
@Component
@Slf4j
@Order(0)
public class RLockAspect {

    ExpressionParser parser = new SpelExpressionParser();

    private final PropertyPlaceholderHelper defaultHelper = new PropertyPlaceholderHelper("# {"."}");

    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Around("@annotation(rLock)")
    public Object lock(ProceedingJoinPoint joinPoint, RLock rLock) throws Throwable {
        String key;
        try {
            key = generateKey(joinPoint, rLock);
        } catch (Exception e) {
            log.error("Failed to generate distributed lock id, execution unit will execute directly!");
            return joinPoint.proceed();
        }
        return RLockUtils.lockExecute(key, rLock.leaseTimeMs(), joinPoint::proceed);
    }


    /** * Generate distributed lock id **@paramJoinPoint connection point *@paramRLock lock annotation *@returnDistributed lock identifier */
    private String generateKey(ProceedingJoinPoint joinPoint, RLock rLock) {
        String key = rLock.key();
        Assert.hasText(key, "Key cannot be configured with whitespace characters!");
        EvaluationContext context = new StandardEvaluationContext();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        if(parameterNames ! =null && parameterNames.length > 0) {
            for (int i = 0; i < parameterNames.length; i++) { context.setVariable(parameterNames[i], args[i]); }}return defaultHelper.replacePlaceholders(key, placeholderName -> {
            Expression expression = parser.parseExpression("#" + placeholderName);
            returnString.valueOf(expression.getValue(context)); }); }}Copy the code

The aspect will automatically intercept all @rlock labeled methods and weave in distributed lock code. There are three spring-related classes to focus on:

  • ParameterNameDiscoverer: Used to get the method parameter name. For more information, please refer toWhy does Spring MVC get method parameter names but not MyBatis?
  • PropertyPlaceholderHelper: used to handle placeholder parsing. For more information, please refer toPropertyPlaceholderHelper document.
  • ExpressionParser: used to parseSpringELExpression. For more information, please refer toExpressionParser document

use

On any method, the @rlock annotation is automatically woven into the distributed lock:

@RLock(key = "abc_#{id}_#{person.name}_#{person.age}")
@SneakyThrows
public void testRLock(Long id, Person person) {
    // Do business processing
    Thread.sleep(5000L);
}
Copy the code

Original is not easy, feel that the article is written well small partners, a praise 👍 to encourage it ~

Welcome to my open source project: a lightweight HTTP invocation framework for SpringBoot