一、目标
- 消除手动开关锁的重复代码,提高代码质量
- 集成不同分布式锁解决方案,并提供统一门面
- 规范锁的命名和异常信息内容
- 避免事务大于锁的优先级,造成幻读
二、锁的基本需求
- 支持根据锁的Key命名,设定锁的范围
- 支持设定锁的等待和持有时间
- 支持快速失败,避免占用系统资源
- 支持未获取锁时,是否抛出异常
- 支持后续扩展(目前实现了单机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);
}
}
后续参考链接