com.ideaaedi.commonspring.lock.DefaultRedisLockSupport Maven / Gradle / Ivy
package com.ideaaedi.commonspring.lock;
import com.ideaaedi.commonds.exception.ValidateException;
import com.ideaaedi.commonds.function.NoArgConsumer;
import com.ideaaedi.commonds.function.NoArgFunction;
import com.ideaaedi.commonds.lock.RedisLockSupport;
import com.ideaaedi.commonds.validate.Validator;
import com.ideaaedi.commonspring.parser.SpelUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
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.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.lang.NonNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* redis分布式锁默认实现
*
* 使用示例见{@link com.ideaaedi.commonspring.RedisLockSupportTest}
*
*
* @author JustryDeng
* @since 2022/4/19 10:08
*/
@Getter
public class DefaultRedisLockSupport implements RedisLockSupport {
/** 获取锁时,不等待,获取不到直接失败 */
public static final long NO_WAIT = 0L;
/** 默认的redisson客户端 */
private static volatile RedissonClient defaultRedissonClient;
/** redisson客户端(优先级高于defaultRedissonClient,当redissonClient不为null时,使用redissonClient) */
protected RedissonClient redissonClient;
/** 锁 key */
protected final String lockKey;
/**
* 锁等待时长 (取值范围:waitTime >= 0)
*
0:表示不等待
*/
protected long waitTime = NO_WAIT;
/**
* 持有锁的最大时长 (取值范围:leaseTime > 0 || leaseTime == -1)
*
-1:表示自动续期
*/
protected long leaseTime = -1L;
/** waitTime和leaseTime的时间单位 */
protected TimeUnit unit = TimeUnit.SECONDS;
public DefaultRedisLockSupport(String lockKey) {
this.lockKey = lockKey;
}
public DefaultRedisLockSupport(RedissonClient redissonClient, String lockKey) {
this.redissonClient = redissonClient;
this.lockKey = lockKey;
}
public DefaultRedisLockSupport(String lockKey, long waitTime, long leaseTime) {
this.lockKey = lockKey;
this.waitTime = waitTime;
this.leaseTime = leaseTime;
}
public DefaultRedisLockSupport(RedissonClient redissonClient, String lockKey, long waitTime, long leaseTime) {
this.redissonClient = redissonClient;
this.lockKey = lockKey;
this.waitTime = waitTime;
this.leaseTime = leaseTime;
}
public DefaultRedisLockSupport(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
this.lockKey = lockKey;
this.waitTime = waitTime;
this.leaseTime = leaseTime;
this.unit = unit;
}
public DefaultRedisLockSupport(RedissonClient redissonClient, String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
this.redissonClient = redissonClient;
this.lockKey = lockKey;
this.waitTime = waitTime;
this.leaseTime = leaseTime;
this.unit = unit;
}
@Override
public R exec(Function
function, P param) throws NotAcquiredRedisLockException {
RedissonClient client = redissonClient();
RLock lock = client.getLock(lockKey);
boolean obtainLock = false;
try {
obtainLock = lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
// ignore
}
if (obtainLock) {
try {
return function.apply(param);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
throw new NotAcquiredRedisLockException(lockKey, waitTime, unit);
}
@Override
public R exec(NoArgFunction function) throws NotAcquiredRedisLockException {
RedissonClient client = redissonClient();
RLock lock = client.getLock(lockKey);
boolean obtainLock = false;
try {
obtainLock = lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
// ignore
}
if (obtainLock) {
try {
return function.apply();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
throw new NotAcquiredRedisLockException(lockKey, waitTime, unit);
}
@Override
public void voidExec(Consumer
consumer, P param) throws NotAcquiredRedisLockException {
RedissonClient client = redissonClient();
RLock lock = client.getLock(lockKey);
boolean obtainLock = false;
try {
obtainLock = lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
// ignore
}
if (obtainLock) {
try {
consumer.accept(param);
return;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
throw new NotAcquiredRedisLockException(lockKey, waitTime, unit);
}
@Override
public void voidExec(NoArgConsumer consumer) throws NotAcquiredRedisLockException {
RedissonClient client = redissonClient();
RLock lock = client.getLock(lockKey);
boolean obtainLock = false;
try {
obtainLock = lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
// ignore
}
if (obtainLock) {
try {
consumer.accept();
return;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
throw new NotAcquiredRedisLockException(lockKey, waitTime, unit);
}
/**
* 获取RedissonClient实例
*
* @return RedissonClient实例
*/
public RedissonClient redissonClient() {
if (this.redissonClient != null) {
return this.redissonClient;
}
if (DefaultRedisLockSupport.defaultRedissonClient != null) {
return DefaultRedisLockSupport.defaultRedissonClient;
}
throw new IllegalStateException("There is not redissonClient available.");
}
/**
* 是否有默认的Redisson客户端
*/
public static boolean hasDefaultRedissonClient() {
return DefaultRedisLockSupport.defaultRedissonClient != null;
}
/**
* 初始化默认的Redisson客户端
*
* @param redissonClient
* Redisson客户端实例
*/
public static void initDefaultRedissonClient(RedissonClient redissonClient) {
if (DefaultRedisLockSupport.defaultRedissonClient != null && !DefaultRedisLockSupport.defaultRedissonClient.equals(redissonClient)) {
throw new IllegalStateException("defaultRedissonClient already been initialized.");
}
synchronized (DefaultRedisLockSupport.class) {
if (DefaultRedisLockSupport.defaultRedissonClient != null) {
if (DefaultRedisLockSupport.defaultRedissonClient.equals(redissonClient)) {
return;
}
throw new IllegalStateException("defaultRedissonClient already been initialized.");
}
DefaultRedisLockSupport.defaultRedissonClient = redissonClient;
}
}
/**
* 方法级分布式锁
*/
@Target(value = ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Synchronized {
/**
* 锁 key (支持spel)
*
* spel表达式(注:因为本方法中用到了{@link TemplateParserContext}, 所以需要使用#{}将原生的spel包起来,形成最终的表达式)
* 1、获取属性示例:{@code #{#param1.fieldA} }
* 2、调用方法示例:{@code #{#param2.methodA()} }
* 3、调用spring-bean示例:{@code #{@userService.getUsername(#userId)}},
* 注:使用之前需要初始化bean解析器{@link SpelUtil#initBeanResolver(BeanResolver)}
* 初始化bean解析器示例:{@code SpelUtil.initBeanResolver(new BeanFactoryResolver(applicationContext)); }
* 4、调用静态字段示例:{@code #{T(org.springframework.core.Ordered).HIGHEST_PRECEDENCE} }
* 注:若调用的是java.lang.包下的类,可以不指定包名
* 5、调用静态方法示例:{@code #{T(java.util.Objects).nonNull(#returnObj)} }
* 注:若调用的是java.lang.包下的类,可以不指定包名
* 6、枚举示例1:{@code {T(com.ideaaedi.demo.enums.CachePrefixEnum).USER_ACCOUNT_PHONE.name()} }
* 注:和调用静态方法是一样的
* 7、枚举示例2:{@code #{T(com.ideaaedi.demo.enums.CachePrefixEnum).USER_ACCOUNT_PHONE.key(#user.account, #user.phone)} }
* 注:和调用静态方法是一样的
* 8、判断示例1:{@code #{#code == 200} }
* 9、判断示例2:{@code #{#user == null} }
* 10、......
*
* 更多spel语法见{@link SpelUtil}
*
*/
String lockKey();
/** 等待获取锁的最大时长 (0表示不等待, 直接获取,无论能否获取到) */
long waitTime() default NO_WAIT;
/** 释放锁的最大时长 */
long leaseTime() default 3L;
/** WaitTime和LeaseTime的时间单位 */
TimeUnit unit() default TimeUnit.SECONDS;
}
/**
* {@link DefaultRedisLockSupport.Synchronized}校验器
*
* @author JustryDeng
* @since 2022/5/17 14:29
*/
@SuppressWarnings("AlibabaAbstractMethodOrInterfaceMethodMustUseJavadoc")
public interface SynchronizedValidator extends Validator> {
@Override
default void validate() throws ValidateException{
throw new UnsupportedOperationException();
}
@Override
default void validate(Supplier> supplier, Function, Boolean> function) throws ValidateException{
throw new UnsupportedOperationException();
}
@Override
default boolean validateAndGet(){
throw new UnsupportedOperationException();
}
@Override
default boolean validateAndGet(Supplier> supplier){
throw new UnsupportedOperationException();
}
@Override
default boolean validateAndGet(Supplier> supplier, Function, Boolean> function){
throw new UnsupportedOperationException();
}
}
/**
* 方法级分布式锁 aop 实现
*
*
* 一般的业务需求下,分布式锁的aop优先级需要大于声明式事务@Transactional的。
*
*
* 此AOP的优先级为{@link Ordered#HIGHEST_PRECEDENCE} + 100
,
* 而声明式事务@Transactional的优先级在{@link org.springframework.transaction.annotation.EnableTransactionManagement#order()}控制,其默认值为
* {@link Ordered#LOWEST_PRECEDENCE}},所以此AOP的优先级是大于声明式事务@Transactional的
*
* @author JustryDeng
* @since 2022/5/14 10:06
*/
@Slf4j
@Aspect
public static class SynchronizedAdvice implements BeanPostProcessor, Ordered {
public static final String BEAN_NAME = "synchronizedAdviceAdvice";
/** aop order */
public static int order = Ordered.HIGHEST_PRECEDENCE + 100;
/** {@link DefaultRedisLockSupport.Synchronized}校验器, 项目启动时校验 */
@Autowired(required = false)
private SynchronizedValidator synchronizedValidator;
/** 若redissonClient为null, 则会使用{@link DefaultRedisLockSupport#defaultRedissonClient} */
@Autowired(required = false)
private RedissonClient redissonClient;
@Around("@annotation(synchronizedAnno)")
public Object aroundAdvice(ProceedingJoinPoint thisJoinPoint, Synchronized synchronizedAnno) throws Throwable {
String lockKeySpel = synchronizedAnno.lockKey();
long waitTime = synchronizedAnno.waitTime();
long leaseTime = synchronizedAnno.leaseTime();
TimeUnit unit = synchronizedAnno.unit();
Method method = ((MethodSignature) thisJoinPoint.getSignature()).getMethod();
Object[] arguments = thisJoinPoint.getArgs();
String lockKey;
if (StringUtils.isNotBlank(lockKeySpel) && lockKeySpel.contains("#")) {
lockKey = SpelUtil.parseSpel(method, arguments, String.class, lockKeySpel);
} else {
lockKey = lockKeySpel;
}
log.debug("lockKeySpel -> {}, lockKey -> {}, waitTime -> {}, leaseTime -> {}, unit -> {}, ", lockKeySpel, lockKey, waitTime, leaseTime, unit);
RedissonClient client = redissonClient == null ? DefaultRedisLockSupport.defaultRedissonClient : redissonClient;
Objects.requireNonNull(client, "redissonClient is null. Please provide redissonClient.");
RLock lock = client.getLock(lockKey);
boolean obtainLock = false;
try {
obtainLock = lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
// ignore
}
if (obtainLock) {
try {
return thisJoinPoint.proceed();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
throw new NotAcquiredRedisLockException(lockKey, waitTime, unit);
}
@Override
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
// 校验
if (synchronizedValidator != null) {
Class> clazz = bean.getClass();
for (Method method : clazz.getMethods()) {
Synchronized annotation = AnnotationUtils.findAnnotation(method, Synchronized.class);
if (annotation != null) {
synchronizedValidator.validate(() -> Pair.of(method, annotation));
}
}
}
return bean;
}
@Override
public int getOrder() {
return SynchronizedAdvice.order;
}
}
}