一、需求
之前写redission分布式锁的时候都是嵌入到程序中,现在把分布式锁用注解的方式去实现,简单整理一下
二、pom依赖
<!-- redission -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.5</version>
</dependency>
三、定义注解
package com.example.redis;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
/**
* redis key
*
* @return
*/
String key() default "";
/**
* 是否开启开门狗 开启看门狗后leaseTime默认为30秒,也可自己配置时间
* 开启看门狗后,只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10秒都会自动再续成30秒
*
* @return
*/
boolean watchDog() default false;
/**
* 重试时间(重试获取锁自旋时间)
*
* @return
*/
int waitTime() default 3;
/**
* 锁的有效时间
*
* @return
*/
int leaseTime() default 3;
/**
* 方法结束是否要释放锁 true-方法执行结束释放锁 false-执行结束不释放锁,等待锁过期释放
*
* @return
*/
boolean methodOverUnLock() default true;
/**
* 时间单位 默认是:秒
*
* @return
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
四、编写切面
package com.example.redis;
import lombok.extern.slf4j.Slf4j;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @Author:
* @Description redission分布式锁
* @Date: 下午5:25 2023/4/25
*/
@Slf4j
@Aspect
@Component
public class RedisLockAspect {
@Resource
private RedissonClient redissonClient;
@Pointcut("@annotation(com.example.redis.RedisLock)")
public void commitPointCut() {
}
@Around("commitPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
return repeatCommit(point);
}
private Object repeatCommit(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("*********Redission分布式锁开始执行*********");
// 获取request对象
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String ip = getIp(request);
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取方法
Method method = signature.getMethod();
// 获取注解
RedisLock commit = method.getAnnotation(RedisLock.class);
//redis key
String redisKey = commit.key();
//时间单位
TimeUnit timeUnit = commit.timeUnit();
//是否开启看门狗
boolean watchDog = commit.watchDog();
//方法结束是否要释放锁
boolean methodOverUnLock = commit.methodOverUnLock();
//获取锁重试时间
int waitTime = commit.waitTime();
//锁的有效时间
int leaseTime = commit.leaseTime();
log.info("*********Redission分布式锁各个参数*********redisKey={},waitTime={},leaseTime={},timeUnit={},watchDog={},methodOverUnLock={}", redisKey, waitTime, leaseTime, timeUnit, watchDog, methodOverUnLock);
if (StringUtils.isBlank(redisKey)) {
redisKey = getRedisKey(ip, method);
}
try {
//获取重入锁对象
RLock lock = redissonClient.getLock(redisKey);
//加锁 如果要开启"看门狗"机制,则leaseTime赋值为"-1"
boolean locked = lock.tryLock(waitTime, true == watchDog ? -1 : leaseTime, timeUnit);
if (!locked) {
log.error("请勿重复提交");
//补充自己获取锁失败的业务逻辑
}
// 继续执行方法
Object proceed = joinPoint.proceed();
log.info("*********Redission分布式锁方法执行结束*********redisKey={},waitTime={},leaseTime={},timeUnit={},watchDog={},methodOverUnLock={}", redisKey, waitTime, leaseTime, timeUnit, watchDog, methodOverUnLock);
return proceed;
} catch (InterruptedException e) {
log.error("*********Redission分布式锁方法执行异常*********redisKey={},waitTime={},leaseTime={},timeUnit={},watchDog={},methodOverUnLock={}", redisKey, waitTime, leaseTime, timeUnit, watchDog, methodOverUnLock);
log.error("*********Redission分布式锁方法执行异常*********e={}", e);
Thread.currentThread().interrupt();
throw e;
} finally {
if (methodOverUnLock) {
unLock(redisKey);
log.info("*********Redission分布式锁释放锁成功*********redisKey={},waitTime={},leaseTime={},timeUnit={},watchDog={},methodOverUnLock={}", redisKey, waitTime, leaseTime, timeUnit, watchDog, methodOverUnLock);
}
}
}
/**
* 如果用户不指定key 则默认获取用户ip+类名+方法名
*
* @param ip
* @param method
* @return
*/
private String getRedisKey(String ip, Method method) {
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
return String.format("%s#%s", ip + className, methodName);
}
/**
* 释放锁
*
* @param lockKey 锁的值
*/
public boolean unLock(String lockKey) {
try {
RLock lock = redissonClient.getLock(lockKey);
if (null != lock && lock.isHeldByCurrentThread()) { //判断锁是否存在,并且判断是否当前线程加的锁。
lock.unlock();
return true;
}
} catch (Exception e) {
log.error(String.format("释放锁%s异常", lockKey));
}
return false;
}
/**
* 获取ip
*
* @param request
* @return
*/
public static String getIp(HttpServletRequest request) {
String unknown = "unknown";
if (request == null) {
return unknown;
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
五、测试类
package com.example.service;
import com.example.redis.RedisLock;
import org.springframework.stereotype.Service;
/**
* @Author: han tao
* @Description
* @Date: 下午5:34 2023/4/25
*/
@Service
public class RedisLockService {
@RedisLock(key = "LOCK_TEST:", watchDog = true, methodOverUnLock = true)
public void testRedisLock() {
try {
Thread.sleep(20000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
六、接口类
package com.example.api;
import com.example.service.RedisLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author:
* @Description
* @Date: 下午5:32 2023/4/25
*/
@RestController
@RequestMapping("/redis-lock")
public class RedisLockApi {
@Autowired
private RedisLockService redisLockService;
/**
* 直接输出到前端
*
* @param
*/
@GetMapping("/test")
public void testRedisLock() {
redisLockService.testRedisLock();
}
}
七、结果
启动项目后,浏览器重复访问:http://localhost:8080/redis-lock/test
查看后台日志即可