SpringBoot 自定义注解+AOP+Redis 实现分布式锁做幂等
-
-
- 1、DistributedLockApi.java(API分布式锁注解)
- 2、DistributedLockTask.java(任务分布式锁注解)
- DistributedLockAspect.java(分布式锁切面)
- 1、TaskService.java(测试任务类)
- 2、Main.java(测试执行任务类)
- 3、ApiController.java(测试ApiController类)
-
GitHub:link. 欢迎star
注意:本篇博客风格(不多比比就是撸代码!!!)
一、maven依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二、自定义注解类
1、DistributedLockApi.java(API分布式锁注解)
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @author Andon
* 2021/12/21
* <p>
* 分布式锁注解(API)
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLockApi {
/**
* 锁key前缀
*/
String key() default "lock_api";
/**
* 锁value
*/
String value() default "lock_value";
/**
* 锁超时时间
*/
long timeout() default 10;
/**
* 超时时间单位
*/
TimeUnit timeUnit() default TimeUnit.MINUTES;
/**
* 被加锁方法执行完是否立即释放锁
*/
boolean immediatelyUnLock() default true;
/**
* 等待获取锁时间(秒)
*/
long waitLockSecondTime() default 0;
}
2、DistributedLockTask.java(任务分布式锁注解)
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @author Andon
* 2021/12/21
* <p>
* 分布式锁注解(任务)
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLockTask {
/**
* 锁key前缀
*/
String key() default "lock_task";
/**
* 锁value
*/
String value() default "lock_value";
/**
* 超时时间
*/
long timeout() default 10;
/**
* 时间单位
*/
TimeUnit timeUnit() default TimeUnit.MINUTES;
/**
* 被加锁方法执行完是否立即释放锁
*/
boolean immediatelyUnLock() default true;
/**
* 等待获取锁时间(秒)
*/
long waitLockSecondTime() default 0;
}
三、切面类
DistributedLockAspect.java(分布式锁切面)
import com.alibaba.fastjson.JSONObject;
import com.andon.springbootdistributedlock.annotation.DistributedLockApi;
import com.andon.springbootdistributedlock.annotation.DistributedLockTask;
import com.andon.springbootdistributedlock.exception.DistributedLockException;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
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.Enumeration;
import java.util.concurrent.TimeUnit;
/**
* @author Andon
* 2021/12/21
* <p>
* 分布式锁切面
*/
@SuppressWarnings("DuplicatedCode")
@Slf4j
@Aspect
@Component
public class DistributedLockAspect {
/**
* StringRedisTemplate
*/
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 任务切点
*/
@Pointcut("@annotation(com.andon.springbootdistributedlock.annotation.DistributedLockTask)")
public void taskPointCut() {
}
/**
* API切点
*/
@Pointcut("@annotation(com.andon.springbootdistributedlock.annotation.DistributedLockApi)")
public void apiPointCut() {
}
/**
* 任务加分布式锁
*/
@Around(value = "taskPointCut() && @annotation(distributedLockTask)")
public Object taskAround(ProceedingJoinPoint pjp, DistributedLockTask distributedLockTask) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
// 获取目标类名方法名
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
// 类名方法名作为分布式锁的key
String key = distributedLockTask.key() + "_" + className + "_" + methodName;
// 获取锁
Boolean status = getLock(key, distributedLockTask.value(), distributedLockTask.timeout(), distributedLockTask.timeUnit(), distributedLockTask.waitLockSecondTime());
if (!ObjectUtils.isEmpty(status) && status.equals(Boolean.TRUE)) {
try {
Object proceed = pjp.proceed();
// 释放锁
if (distributedLockTask.immediatelyUnLock()) {
//是否立即释放锁
unLock(key);
}
return proceed;
} catch (Throwable throwable) {
log.error("key failure!! key{} error:{}", key, throwable.getMessage());
}
}
log.warn("getLock failure!! key{} 已执行!!", key);
return null;
}
/**
* API加分布式锁
*/
@Around(value = "apiPointCut() && @annotation(distributedLockApi)")
public Object apiAround(ProceedingJoinPoint pjp, DistributedLockApi distributedLockApi) throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String remoteHost = request.getRemoteHost(); //访问者ip
String method = request.getMethod(); //请求方式
String uri = request.getRequestURI(); //请求路径
Enumeration<String> headerNames = request.getHeaderNames();
JSONObject headers = new JSONObject();
while (headerNames.hasMoreElements()) {
String s = headerNames.nextElement();
headers.put(s, request.getHeader(s)); //请求头
}
log.info("headers:{}", headers.toJSONString());
Object[] args = pjp.getArgs(); //获取入参
// 类名方法名作为分布式锁的key
String key = distributedLockApi.key() + "_" + method + "_" + uri + "_" + JSONObject.toJSONString(args);
// 获取锁
Boolean status = getLock(key, distributedLockApi.value(), distributedLockApi.timeout(), distributedLockApi.timeUnit(), distributedLockApi.waitLockSecondTime());
if (!ObjectUtils.isEmpty(status) && status.equals(Boolean.TRUE)) {
try {
Object proceed = pjp.proceed();
// 释放锁
if (distributedLockApi.immediatelyUnLock()) {
//是否立即释放锁
unLock(key);
}
return proceed;
} catch (Throwable throwable) {
log.error("key failure!! key{} error:{}", key, throwable.getMessage());
}
}
log.warn("getLock failure!! key{} 已执行!!", key);
throw new DistributedLockException("请勿重复操作!!");
}
/**
* 获取锁
*/
private Boolean getLock(String key, String value, long timeout, TimeUnit unit, long waitLockSecondTime) {
Boolean status = null;
try {
long endTime = System.currentTimeMillis() + waitLockSecondTime * 1000;
do {
status = stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
Thread.sleep(50);
} while (System.currentTimeMillis() - endTime < 0 && (ObjectUtils.isEmpty(status) || !status.equals(Boolean.TRUE)));
} catch (Exception e) {
log.error("getLock failure!! error:{}", e.getMessage());
}
return status;
}
/**
* 释放锁
*/
private void unLock(String key) {
try {
stringRedisTemplate.delete(key);
} catch (Exception e) {
log.error("unLock failure!! error:{}", e.getMessage());
}
}
}
四、测试类
1、TaskService.java(测试任务类)
import com.andon.springbootdistributedlock.annotation.DistributedLockTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @author Andon
* 2021/12/21
*/
@Slf4j
@Service
public class TaskService {
@DistributedLockTask(timeout = 1, timeUnit = TimeUnit.DAYS, immediatelyUnLock = false)
public void task01() {
log.info("task01 run!!");
}
}
2、Main.java(测试执行任务类)
import com.andon.springbootdistributedlock.task.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author Andon
* 2021/12/21
*/
@Slf4j
@Component
public class Main implements ApplicationRunner {
@Resource
private ThreadPoolExecutor globalExecutorService;
@Resource
private TaskService taskService;
@Override
public void run(ApplicationArguments args) {
for (int i = 0; i < 6; i++) {
System.out.println("Run!!");
}
globalExecutorService.execute(() -> taskService.task01());
}
}
3、ApiController.java(测试ApiController类)
import java.util.concurrent.TimeUnit;
/**
* @author Andon
* 2021/12/21
*/
@Slf4j
@RequestMapping(value = "/api")
@RestController
public class ApiController {
@DistributedLockApi(timeout = 20, timeUnit = TimeUnit.SECONDS, immediatelyUnLock = false, waitLockSecondTime = 5)
@GetMapping(value = "/test")
public String test() {
log.info("test!!");
return "test!!";
}
}
五、测试结果
1、任务-加分布式锁
2、Api-加分布式锁