1 前言
在我们项目开发中,分布式锁基本上都会用到。现在用的最多的就是redission,但是在实际开发中加锁和释放锁稍不注意就会出问题。为了规避低级错误和方便使用,我最近基于redission封装了一个工具类,可以优雅的对我们方法加分布式锁。
2 使用方式
举例,我们有这样一个方法: 会员余额变更,为了防止多实例并发情况下余额错乱问题,在做会员余额变更时 我们需要对当前会员加锁。加锁时我们只用在方法上加自定义的lock注解 ,在方法参数中加lockkey注解。根据业务我们这里是需要对当前会员加锁,所以我们在memberid上加lockkey注解,加上这两个注解后,加锁就完成了。
3 代码实现原理
3.1 目录结构
3.2 首先定义了三个注解
-
EnableRedisson,它可以放到我们springboot的启动类上面,表示我们当前项目需要用到分布式锁,根据配置会把我们当前分布式锁的相关类初始化到我们spring容器中。代码如下:
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; import com.mrxu.framework.starter.redisson.LockAutoConfiguration; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(LockAutoConfiguration.class) public @interface EnableRedisson { } -
lock 主要作用于方法上,定义锁的一些相关属性,可以设置锁的类型为:自旋锁、阻塞锁。锁的持续时间,自旋时间,锁的前缀,获取锁失败后的提示。代码如下:
import com.mrxu.framework.starter.redisson.bean.LockPolicy; import java.lang.annotation.*; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Lock { /** * 锁的策略 */ LockPolicy policy() default LockPolicy.SpinLock; /** * 锁持续时间 单位秒 默认1分钟 */ int leaseTime() default 60; /** * 自旋锁超时时间 单位毫秒 */ long waitTime() default 2000L; /** * redis锁的key前缀 如果为空,则默认为类名+方法名 */ String keyPrex() default ""; /** * 未获取资源锁提示 */ String failMsg() default "请勿重复操作"; }锁类型枚举:
public enum LockPolicy { /** * 默认锁 失败立即返回 */ Lock, /** * 自旋锁 超时时间timeout */ SpinLock, /** * 阻塞锁 无超时自旋 */ BlockLock } -
lockkey 作用于方法的参数上,用于缩小锁的范围。代码如下:
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LockKey { /** * 用在model参数前时, 需指定用作key的字段名。 */ String[] keyField() default {}; }
3.3 核心实现类
LockManager,项目通过EnableRedisson,会把它引入初始化到spring容器中,它拦截了所有lock注解的方法,做了个切面,然后根据注解计算出分布式锁的key 然后在方法调用前进行加锁,finally方法里 释放锁。代码如下:
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import com.mrxu.framework.common.util.MrxuAssert;
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.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.mrxu.framework.starter.redisson.anno.Lock;
import com.mrxu.framework.starter.redisson.anno.LockKey;
import com.mrxu.framework.starter.redisson.bean.LockPolicy;
/**
* @version v1
* @summary 分布式锁管理
@Lock(policy= LockPolicy.SpinLock,leaseTime=60,waitTime=2000l,keyPrex="",failMsg="测试")
public ResponseObj<PageResult<StoreInfo>> list(@LockKey @RequestParam("tenantId")String tenantId,@LockKey(keyField="tenantId") @RequestBody StoreDto dto)
*/
@Aspect
@Component
public class LockManager {
private static Logger logger = LoggerFactory.getLogger(LockManager.class);
@Autowired
private RedissonClient redissonClient;
@Around(value = "@annotation(lock)", argNames = "pjp, lock")
public Object around(ProceedingJoinPoint pjp,Lock lock) throws Throwable {
Class<? extends Object> clazz = pjp.getTarget().getClass();
String methodName = pjp.getSignature().getName();
//获取参数
Object[] args = pjp.getArgs();
MethodSignature ms = (MethodSignature) pjp.getSignature();
Method m = ms.getMethod();
//获取锁前缀,如果没配置 取方法名
String keyPrix = lock.keyPrex();
if ("".equals(keyPrix)) {
keyPrix = clazz.getName() + ":" + methodName + ":";
}
//分布式锁的key
StringBuilder lockKey = new StringBuilder("lock:" + keyPrix);
//获取加注解的方法参数的值
Annotation[][] parameterAnnotations = m.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] annotations = parameterAnnotations[i];
for (Annotation annotation : annotations) {
if (annotation.annotationType() == LockKey.class) {
LockKey anno = (LockKey) annotation;
if (anno.keyField() != null && anno.keyField().length > 0) {
lockKey.append(genModelKey(args[i], anno.keyField()));
}
else {
lockKey.append("_");
lockKey.append(args[i]);
}
}
}
}
logger.debug("分布式锁key:{}",lockKey.toString());
LockPolicy policy = lock.policy();
RLock fairLock = redissonClient.getFairLock(lockKey.toString());
boolean getLock = false;
if(policy.equals(LockPolicy.Lock)) {
getLock = fairLock.tryLock();
}
else if(policy.equals(LockPolicy.SpinLock)) {
getLock = fairLock.tryLock(lock.waitTime(),lock.leaseTime(),TimeUnit.SECONDS);
}
else {
getLock = fairLock.tryLock(Long.MAX_VALUE,lock.leaseTime(),TimeUnit.SECONDS);
}
//获取不到锁直接终止流程
MrxuAssert.isTrue(getLock,lock.failMsg());
try {
return pjp.proceed();
}
finally {
fairLock.unlock();
logger.debug("解锁key:{}",lockKey.toString());
}
}
private String genModelKey(Object model, String[] fields) throws Exception {
StringBuilder sb = new StringBuilder();
for (String field : fields) {
sb.append("_");
try {
sb.append(model.getClass().getMethod(buildGetMethod(field)).invoke(model));
}
catch (Exception e) {
logger.error("redisLock error: keyField '{}' 不存在", field, e);
throw e;
}
}
return sb.toString();
}
private String buildGetMethod(String field) {
StringBuilder sb = new StringBuilder("get");
sb.append(Character.toUpperCase(field.charAt(0)));
sb.append(field.substring(1, field.length()));
return sb.toString();
}
}
3.4 项目引入方式说明
项目启动类通过EnableRedisson引入LockAutoConfiguration实例然后通过它上面的注解@Import(LockAutoConfiguration.class) 引入LockAutoConfiguration类,这个类里面定义了Spring扫描分布式锁的相关类的包名。代码如下:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@ComponentScan({"com.mrxu.framework.starter.redisson"})
@EnableAspectJAutoProxy
public class LockAutoConfiguration {
}
4 总结
整个过程就是这样的,如果你也写这样一个工具类欢迎点赞收藏,方面以后参考。