项目用到了redisson分布式锁,但是每个地方的代码除了业务代码,其他都差不多一样的,如果要修改的话,就要修改很多,不只修改一个项目,其他项目也得该,地方多了容易出错,并且数据也对不上。起始的想法是封装起来,然后自定义一个spring boot starter。其他项目依赖即可,这样一来如果要修改只改这个starter模块就可以了。于是将项目分布式锁进行了简单封装,以前是下面代码这样的
@Autowired
private RedissonClient redissonClient;
public void syncHuifuBill() throws InterruptedException {
RLock redLock = redissonClient.getLock(profiles+"_HUIFU_REDLOCK_KEY");
boolean b = redLock.tryLock(0,30000,TimeUnit.MILLISECONDS);
try {
//自己的业务逻辑
} catch (Exception e) {
logger.error("交易对账单同步数据库失败", e);
}finally {
if(b){
if(redLock.isLocked()){
redLock.unlock();
}
}
}
}
每个地方都是try catch finally这种类似的,代码看着也不舒服,后期维护动的代码也比较多。
解决办法
下面代码只是简单写了下两种思路,实际项目中不止这点
①自定义注解
需要在枷锁的方法上面贴一个注解,利用Spring AOP动态代理的特性进行处理,配置自己的参数值即可
自定义注解
package com.example.ann;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author yupanpan
* @Description: 锁注解
* @date 2020/9/3 11:22
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributeLock {
/**
* @author 锁的key前缀
*/
String lockKeyPrefix() default "";
/**
* @author 业务code
*/
String businessCode() default "";
/**
* @author 获取锁的等待时间(毫秒)
*/
int waitTime() default 0;
/**
* @author 释放的时间(毫秒)
*/
int leaseTime() default 0;
}
AOP
package com.example.aop;
import com.example.ann.DistributeLock;
import com.example.exception.DistributeLockException;
import org.apache.commons.lang3.StringUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @author yupanpan
* @Description: 分布式锁切面
* @date 2020/9/3 11:45
*/
@Aspect
@Component
public class DistributeLockAspect {
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(com.example.ann.DistributeLock)")
public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
DistributeLock distributeLock = method.getAnnotation(DistributeLock.class);
String lockKey=getLockKeyName(distributeLock,joinPoint);
RLock rLock=redissonClient.getLock(lockKey);
boolean b=getLock(rLock,distributeLock);
try {
if(b){
return joinPoint.proceed();
}else {
throw new DistributeLockException("网络繁忙,请稍后再试");
}
}catch (Exception e){
throw new DistributeLockException("服务器繁忙,请稍后再试");
}finally {
if(b){
if(rLock.isLocked()){
rLock.unlock();
}
}
}
}
private String getLockKeyName(DistributeLock distributeLock,ProceedingJoinPoint joinPoint) {
String lockKeyPrefix = distributeLock.lockKeyPrefix();
String businessCode = distributeLock.businessCode();
if(StringUtils.isNotBlank(lockKeyPrefix)&&StringUtils.isNotBlank(businessCode)){
return lockKeyPrefix+":"+businessCode;
}else if(StringUtils.isNotBlank(lockKeyPrefix)){
return lockKeyPrefix;
}else if(StringUtils.isNotBlank(businessCode)){
return businessCode;
}else {
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
return className+"."+methodName;
}
}
private boolean getLock(RLock rLock, DistributeLock distributeLock) throws InterruptedException {
int waitTime = distributeLock.waitTime();
int leaseTime = distributeLock.leaseTime();
boolean b=false;
if(waitTime>0&&leaseTime>0){
b=rLock.tryLock(waitTime,leaseTime, TimeUnit.MICROSECONDS);
}else if(leaseTime>0){
b=rLock.tryLock(0,leaseTime,TimeUnit.MILLISECONDS);;
}else if(waitTime>0){
b=rLock.tryLock(waitTime,TimeUnit.MILLISECONDS);
}else {
b=rLock.tryLock();
}
return b;
}
}
测试使用
@DistributeLock(lockKeyPrefix = "MAIL", businessCode = "ABEODS", waitTime = 0, leaseTime = 30000)
@GetMapping("/distribute/lock")
public String distributeLock() throws Exception {
return "分布式锁注解测试....";
}
②封装获取锁的过程
直接调用工具类方法,只关心业务逻辑处理部分,可以拓展成多个接口实现类实现不同的业务锁
package com.example.service.lock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* @author yupanpan redisson分布式锁工具类
* @Description: a
* @date 2020/9/3 11:14
*/
@Service
@Transactional
public class RedisDistributeLock {
@Autowired
private RedissonClient redissonClient;
public <T> T lock(String key, int waitTime, int leaseTime, Supplier<T> success,Supplier<T> fail) throws Exception {
RLock lock = redissonClient.getLock(key);
boolean b = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
try {
if(b){
return success.get();
}else {
return fail.get();
}
}finally {
if(b){
if(lock.isLocked()){
lock.unlock();
}
}
}
}
}
函数式接口 Supplier<T>
T:出参类型;没有入参
调用方法:T get();
定义函数示例:Supplier<Integer> supplier= () -> 100; // 常用于业务“有条件运行”时,符合条件再调用获取结果的应用场景;运行结果须提前定义,但不运行。
调用函数示例:supplier.get();
测试使用
@GetMapping("/lock")
public String lock() throws Exception {
return redisDistributeLock.lock("MAIL",0,30000,()->{
//获取锁成功执行逻辑
return "获取锁成功";
},()->{
//获取锁失败执行逻辑
return "获取锁失败";
});
}
区别
维护都比较方便。第一种适用于方法级别的,代码更优雅舒服一些。第二种通用,整个方法全部代码块和部分代码块都可,业务拓展性更高