AOP锁注解SyncLock,一步解决各种锁的集成

一、目标

  1. 消除手动开关锁的重复代码,提高代码质量
  2. 集成不同分布式锁解决方案,并提供统一门面
  3. 规范锁的命名和异常信息内容
  4. 避免事务大于锁的优先级,造成幻读

二、锁的基本需求

  1. 支持根据锁的Key命名,设定锁的范围
  2. 支持设定锁的等待和持有时间
  3. 支持快速失败,避免占用系统资源
  4. 支持未获取锁时,是否抛出异常
  5. 支持后续扩展(目前实现了单机JVM锁和基于Redis的Redisson分布式)

三、设计思想

通过springAOP和order注解在方法上切入解决事务和锁的优先级,提供统一开关锁接口loackManager集成不同分布式锁作为锁管理器,使用springEL表达式规范锁的命名和异常信息内容

四、代码结构

│
├─annotation
│      SyncLock.java
│
├─config
│      SyncLockConfig.java
│
├─core
│  │  LockManager.java
│  │  SyncLockAspect.java
│  │
│  ├─jvm
│  │      JvmLockManager.java
│  │
│  └─redisson
│          RedissonLockManager.java
│
├─enums
│      LockManagerEnum.java
│
└─exception
        TryLockException.java

五、详细设计

SyncLock.java

package cn.edu.cole.springboot.starter.log.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({METHOD})
@Retention(RUNTIME)
public @interface SyncLock {

    /**
     * 锁的管理器
     *
     * @return 锁的管理器
     */
    String manager() default "REDISSON";

    /**
     * 锁的key
     *
     * @return 锁的key
     */
    String[] key();

    /**
     * 错误信息
     *
     * @return 错误信息
     */
    String[] errorMsg() default "";

    /**
     * 快速失败,未获取到锁,直接取消等待 默认false
     *
     * @return 快速失败
     */
    boolean fastFail() default false;

    /**
     * 未获取到锁,是否抛出异常
     *
     * @return 默认true
     */
    boolean throwError() default true;

    /**
     * 锁的持有时间,默认实现中仅有REDISSON支持 (默认10S,值未负数时,默认一直持有)
     *
     * @return 锁的持有时间
     */
    long leaseTime() default 10;

    /**
     * 获取锁的最大等待时间,超过这个时间则认为获取锁失败(默认3S)
     *
     * @return 获取锁的最大等待时间
     */
    long waitTime() default 3;

    /**
     * 锁的等待时间单位
     *
     * @return 锁的等待时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

SyncLockConfig.java

package cn.edu.cole.springboot.starter.log.config;

import cn.edu.cole.springboot.starter.base.util.ApplicationContextHolder;
import cn.edu.cole.springboot.starter.log.core.jvm.JvmLockManager;
import cn.edu.cole.springboot.starter.log.core.SyncLockAspect;
import cn.edu.cole.springboot.starter.log.core.redisson.RedissonLockManager;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(ApplicationContextHolder.class)
public class SyncLockConfig {

    @Bean
    SyncLockAspect syncLockAspect() {
        return new SyncLockAspect();
    }

    @Bean
    JvmLockManager jvmLockManager() {
        return new JvmLockManager();
    }

    @Bean
    @ConditionalOnClass(RedissonClient.class)
    RedissonLockManager redissonLockManager(RedissonClient redissonClient) {
        return new RedissonLockManager(redissonClient);
    }
}

LockManager.java

package cn.edu.cole.springboot.starter.log.core;

import cn.edu.cole.springboot.starter.log.annotation.SyncLock;

import java.util.concurrent.locks.Lock;

/**
 * 锁的管理器接口
 */
public interface LockManager {

    /**
     * 获取锁的管理器名称
     *
     * @return 锁的管理器名称
     */
    String getLockManagerName();

    /**
     * 获取锁
     * @param key 锁的Key
     * @return 锁
     */
    Lock getLock(String key);

    /**
     * 尝试获取锁
     * @param syncLock 注解信息
     * @param lock 锁
     * @return 是否获取到锁
     * @throws Exception 异常
     */
    boolean tryLock(SyncLock syncLock, Lock lock) throws Exception;

    /**
     * 释放锁
     * @param syncLock 注解信息
     * @param lock 锁
     */
    void unLock(SyncLock syncLock, Lock lock);
}

SyncLockAspect.java

package cn.edu.cole.springboot.starter.log.core;

import cn.edu.cole.springboot.starter.base.util.ApplicationContextHolder;
import cn.edu.cole.springboot.starter.base.util.SpELUtil;
import cn.edu.cole.springboot.starter.log.annotation.SyncLock;
import cn.edu.cole.springboot.starter.log.exception.TryLockErrorException;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.Order;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;


@Slf4j
@Aspect
@Order(0)
public class SyncLockAspect implements InitializingBean  {

    private final Map<String, LockManager> lockManagerMap = new HashMap<>();

    @Around("@annotation(cn.edu.cole.springboot.starter.log.annotation.SyncLock)")
    public Object syncLockHand(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = joinPoint.getTarget().getClass()
                .getDeclaredMethod(methodSignature.getName(), methodSignature.getParameterTypes());
        SyncLock syncLock = method.getAnnotation(SyncLock.class);

        String LOCK_KEY_PREFIX = "SyncLock:";
        String LOCK_KEY_PREFIX_SPLIT = ":";
        String lockKey = LOCK_KEY_PREFIX + Arrays.stream(syncLock.key())
                .map(k ->  SpELUtil.parseKey(k, method, joinPoint.getArgs()))
                .map(String::valueOf)
                .collect(Collectors.joining(LOCK_KEY_PREFIX_SPLIT));
        LockManager lockManager = getLockManager(syncLock.manager());
        Lock lock = lockManager.getLock(lockKey);

        boolean locked = false;
        try {
            locked = lockManager.tryLock(syncLock, lock);
        } catch (Exception e) {
            throw new TryLockErrorException(e);
        }

        log.info("获取锁的名称[{}]键[{}]结果:[{}]", lockManager.getLockManagerName(), lockKey, locked);

        //判断是否获取到锁
        if (locked) {
            try {
                return joinPoint.proceed();
            } finally {
                lockManager.unLock(syncLock, lock);
            }
        } else if (syncLock.throwError()) {
            String errorMsg = StrUtil.isAllBlank(syncLock.errorMsg()) ?
                    StrUtil.format("锁[{}]已被占用,请稍后再试!", lockKey) :
                    Arrays.stream(syncLock.key())
                            .map(k ->  SpELUtil.parseKey(k, method, joinPoint.getArgs()))
                            .map(String::valueOf)
                            .collect(Collectors.joining(LOCK_KEY_PREFIX_SPLIT));
            throw new IllegalArgumentException(errorMsg);
        } else {
            log.error("获取锁失败[{}]", lockKey);
            return null;
        }
    }

    private LockManager getLockManager(String lockManagerName) {
        LockManager lockManager = lockManagerMap.get(lockManagerName);
        if (null == lockManager) throw new RuntimeException(StrUtil.format("未找到锁管理器:{}", lockManagerName));
        return lockManager;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, LockManager> lockManagers = ApplicationContextHolder.getBeansOfType(LockManager.class);
        lockManagers.forEach((k, v) -> lockManagerMap.put(v.getLockManagerName(), v));
        log.info("已安装同步锁管理器:{}", this.lockManagerMap.keySet());
    }
}

JvmLockManager.java

package cn.edu.cole.springboot.starter.log.core.jvm;

import cn.edu.cole.springboot.starter.log.annotation.SyncLock;
import cn.edu.cole.springboot.starter.log.core.LockManager;
import cn.edu.cole.springboot.starter.log.enums.LockManagerEnum;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * JVM锁实现
 */
public class JvmLockManager implements LockManager {

    /**
     * 锁缓存,解决锁的重入问题
     */
    private final Map<String, Lock> cache = new ConcurrentHashMap<>();

    @Override
    public String getLockManagerName() {
        return LockManagerEnum.JVM.name();
    }

    @Override
    public Lock getLock(String key) {
        return cache.computeIfAbsent(key, k -> new ReentrantLock());
    }

    @Override
    public boolean tryLock(SyncLock syncLock, Lock lock) throws Exception {
        return syncLock.fastFail() ?
                lock.tryLock() :
                lock.tryLock(syncLock.waitTime(), syncLock.timeUnit());
    }

    @Override
    public void unLock(SyncLock syncLock, Lock lock) {
        lock.unlock();
    }

}

RedissonLockManager.java

package cn.edu.cole.springboot.starter.log.core.redisson;

import cn.edu.cole.springboot.starter.log.annotation.SyncLock;
import cn.edu.cole.springboot.starter.log.core.LockManager;
import cn.edu.cole.springboot.starter.log.enums.LockManagerEnum;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.locks.Lock;

/**
 * Redisson分布式锁管理器
 */
@RequiredArgsConstructor
public class RedissonLockManager implements LockManager {

    private final RedissonClient redissonClient;

    @Override
    public String getLockManagerName() {
        return LockManagerEnum.REDISSON.name();
    }

    @Override
    public Lock getLock(String key) {
        return redissonClient.getLock(key);
    }

    @Override
    public boolean tryLock(SyncLock syncLock, Lock lock) throws Exception {
        RLock rLock = (RLock) lock;
        return syncLock.fastFail() ?
                rLock.tryLock() :
                syncLock.leaseTime() > 0 ?
                        rLock.tryLock(syncLock.waitTime(), syncLock.leaseTime(), syncLock.timeUnit()) :
                        rLock.tryLock(syncLock.waitTime(), syncLock.timeUnit());
    }

    @Override
    public void unLock(SyncLock syncLock, Lock lock) {
        RLock rLock = (RLock) lock;
        if (rLock.isHeldByCurrentThread()) {
            rLock.unlock();
        }
    }

}

LockManagerEnum.java

package cn.edu.cole.springboot.starter.log.enums;

public enum LockManagerEnum{

    /**
     * JVM单机锁
     */
    JVM,

    /**
     * Redisson分布式锁
     */
    REDISSON

}

TryLockException.java

package cn.edu.cole.springboot.starter.log.exception;

public class TryLockErrorException extends RuntimeException {

    public TryLockErrorException() {
        super();
    }

    public TryLockErrorException(String message) {
        super(message);
    }

    public TryLockErrorException(String message, Throwable cause) {
        super(message, cause);
    }

    public TryLockErrorException(Throwable cause) {
        super(cause);
    }
}

SpELUtil.java

package cn.edu.cole.springboot.starter.base.util;

import cn.hutool.core.util.ArrayUtil;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;

/**
 * SpEL 表达式解析工具
 */
public class SpELUtil {
    
    /**
     * 校验并返回实际使用的 spEL 表达式
     *
     * @param spEl spEL 表达式
     * @return 实际使用的 spEL 表达式
     */
    public static Object parseKey(String spEl, Method method, Object[] contextObj) {
        String spElFlag = "#";
        if (!spEl.contains(spElFlag)) {
            return spEl;
        }
        return parse(spEl, method, contextObj);
    }
    
    /**
     * 转换参数为字符串
     *
     * @param spEl       spEl 表达式
     * @param contextObj 上下文对象
     * @return 解析的字符串值
     */
    public static Object parse(String spEl, Method method, Object[] contextObj) {
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(spEl);
        String[] params = discoverer.getParameterNames(method);
        StandardEvaluationContext context = new StandardEvaluationContext();
        if (ArrayUtil.isNotEmpty(params)) {
            for (int len = 0; len < params.length; len++) {
                context.setVariable(params[len], contextObj[len]);
            }
        }
        return exp.getValue(context);
    }
}

ApplicationContextHolder.java

package cn.edu.cole.springboot.starter.base.util;

import jakarta.annotation.Nonnull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.annotation.Annotation;
import java.util.Map;

/**
 * Application context holder.
 */
public class ApplicationContextHolder implements ApplicationContextAware {
    
    private static ApplicationContext CONTEXT;
    
    @Override
    public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
        ApplicationContextHolder.CONTEXT = applicationContext;
    }
    
    /**
     * Get ioc container bean by type.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return CONTEXT.getBean(clazz);
    }
    
    /**
     * Get ioc container bean by name and type.
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return CONTEXT.getBean(name, clazz);
    }
    
    /**
     * Get a set of ioc container beans by type.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
        return CONTEXT.getBeansOfType(clazz);
    }
    
    /**
     * Find whether the bean has annotations.
     *
     * @param beanName
     * @param annotationType
     * @param <A>
     * @return
     */
    public static <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) {
        return CONTEXT.findAnnotationOnBean(beanName, annotationType);
    }
    
    /**
     * Get ApplicationContext.
     *
     * @return
     */
    public static ApplicationContext getInstance() {
        return CONTEXT;
    }
}

六、使用测试案例

public interface LockService {

    String doSomethingWithJvmLock(String name, Integer age);

    String doSomethingWithRedissonLock(String name, Integer age);
}


@Service
public class LockServiceImpl implements LockService {

    @SyncLock(key = {"#name", "#age"}, manager = "JVM", fastFail = true)
    @Override
    public String doSomethingWithJvmLock(String name, Integer age) {
        return "doSomethingWithJvmLock";
    }

    @SyncLock(key = "#age", manager = "REDISSON", fastFail = true)
    @Override
    public String doSomethingWithRedissonLock(String name, Integer age) {
        return "doSomethingWithRedissonLock";
    }
}

@Component
@SpringBootTest
class SyncLockTest {

    @Resource private LockService lockService;

    @Test
    void jvmLock() {
        String s = lockService.doSomethingWithJvmLock("jvm", null);
        System.out.println(s);
    }

    @Test
    void redissonLock() {
        String s = lockService.doSomethingWithRedissonLock("redisson", 22);
        System.out.println(s);
    }
}

后续参考链接

1、分布式锁注解SyncLock

2、jdbc-lock-registry

3、redis-lock-registry

4、zk-lock-registry

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值