@RedisLock注解实现分布式锁

1.@RedisLock注解

package com.dstcar.common.utils.lock;

import java.lang.annotation.*;

/**
 * @author: liu wei ping
 * @date: 2022/8/15 16:39
 */
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {

    /** 缓存Key,支持SPEL表达式 **/
    String value() default "";

    String tips() default "重复请求";

    /** 过期时间(毫秒) **/
    long expire() default 3000;

    /** 最大过期时间(毫秒) **/
    long maxExpire() default 300000;

    /** 方法执行完释放 **/
    boolean quitRelease() default true;
}

2.使用切片类RedisDistributedLockAspect监听@RedisLock主解

package com.dstcar.common.utils.lock;

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.RedisTemplate;
import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 分布式时锁,用于防重提交,注意:业务内部还是要进行重复校验
 配置
 @Bean
 public RedisDistributedLockAspect createRedisDistributedLockAspect(ObjectProvider<RedisTemplate> redisTemplate){
    RedisDistributedLockAspect aspect = new RedisDistributedLockAspect();
    aspect.setRedisTemplate(redisTemplate.getIfAvailable());
    return aspect;
 }
 * @author: liu wei ping
 * @date: 2022/8/15 16:35
 */
@Aspect
public class RedisDistributedLockAspect {
    public static final String KEY_PREFIX = "DUPLICATION_SUBMIT:";

    private RedisTemplate redisTemplate;

    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 切点
     * @author: liu wei ping
     * @date: 2022/08/15 16:45
     **/
    @Pointcut("@annotation(com.dstcar.common.utils.lock.RedisLock)")
    public void getPointcut(){}

    /**
     *
     **/
    @Around("getPointcut()")
    public Object doBefore(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Object result;
        if (null != method && method.isAnnotationPresent(RedisLock.class)){
            RedisLock redisLock = method.getAnnotation(RedisLock.class);
            String lockKey = 
            KEY_PREFIX+SpELAspectSupport.getKeyValue(point,redisLock.value());
            String requestId = UUID.randomUUID().toString();
            long expireTime = redisLock.expire();
            if (redisLock.quitRelease()){
                expireTime = redisLock.maxExpire();
            }
            boolean res = tryGetDistributedLock(lockKey,requestId,expireTime);
            if (!res){
                throw new DistributedLockException(redisLock.tips());
            }
            try {
                result = point.proceed();
            }finally {
                if (redisLock.quitRelease()){
                    releaseDistributedLock(lockKey);
                }
            }
        }else {
            result = point.proceed();
        }
        return result;
    }

    /**
     * 尝试获取分布式锁
     * @param lockKey    锁
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public boolean tryGetDistributedLock(String lockKey, String requestId,long 
       expireTime) {
        return  redisTemplate.opsForValue().
        setIfAbsent(lockKey,requestId,expireTime,TimeUnit.MILLISECONDS);
    }

    /**
     * 释放分布式锁
     *
     * @param lockKey   锁
     * @return 是否释放成功
     */
    public boolean releaseDistributedLock(String lockKey) {
        return redisTemplate.delete(lockKey);
    }
}

 3.切面的SPEL表达式工具类SpELAspectSupport

package com.dstcar.common.utils.lock;

import com.dstcar.common.utils.json.JsonUtil;
import com.dstcar.common.utils.string.MD5Helper;
import org.aspectj.lang.JoinPoint;

/**
 * 
 * @author: liu wei ping
 * @date: 2022/8/15 16:48
 */
public class SpELAspectSupport {
    private static final ExpressionEvaluator evaluator = new ExpressionEvaluator();

    public static String getKeyValue(JoinPoint jp,String keyExpression){
        Object[] args = jp.getArgs();
        String keyValue;
        if (null != keyExpression && !"".equals(keyExpression.trim())){
            keyValue = evaluator.getKey(keyExpression,jp);
        }else if (args != null && args.length > 0 && args[0] != null){
            keyValue = MD5Helper.encrypt32(JsonUtil.toJson(args[0]));
        }else {
            throw new IllegalArgumentException("key或方法入参必须存在一个且不可为null");
        }
        return keyValue;
    }

}

4.SPEL表达式翻译类

package com.dstcar.common.utils.lock;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 
 * @author: liu wei ping
 * @date: 2022/8/15 16:29
 */
public class ExpressionEvaluator extends CachedExpressionEvaluator {

    private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);

    private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<> 
  (64);

    /**
     * 解析key
     * @author: liu wei ping
     * @date: 2022/8/15 16:29
     **/
    public String getKey(String conditionExpression,JoinPoint jp){
        Object[] args = jp.getArgs();
        Object target = jp.getTarget();
        Class<?> targetClass = target.getClass();
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();
        EvaluationContext evalContext = 
        createEvaluationContext(target,targetClass,method,args);
        AnnotatedElementKey elementKey = new AnnotatedElementKey(method,targetClass);
        return getExpression(this.conditionCache, elementKey, 
         conditionExpression).getValue(evalContext,String.class);
    }

    private EvaluationContext createEvaluationContext(Object target, Class<?> 
        targetClass, Method method, Object[] args) {
        Method targetMethod = getTargetMethod(targetClass,method);
        ExpressionRootObject rootObject = new ExpressionRootObject(target,args);
        return new MethodBasedEvaluationContext(
                rootObject, targetMethod, args, getParameterNameDiscoverer());
    }


    /**
     * 获取目标方法
     **/
    private Method getTargetMethod(Class<?> targetClass,Method method){
        AnnotatedElementKey methodKey = new AnnotatedElementKey(method,targetClass);
        Method targetMethod = this.targetMethodCache.get(methodKey);
        if (targetMethod == null){
            targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
            if (targetMethod == null) {
                targetMethod = method;
            }
            this.targetMethodCache.put(methodKey, targetMethod);
        }
        return targetMethod;
    }

}


package com.dstcar.common.utils.lock;

/**
 * 
 * @author: liu wei ping
 * @date: 2022/08/15 18:13
 */
public class ExpressionRootObject {

    private final Object obj;
    private final Object[] args;


    public ExpressionRootObject(Object obj, Object[] args) {
        this.obj = obj;
        this.args = args;
    }


    public Object getObj() {
        return obj;
    }

    public Object[] getArgs() {
        return args;
    }
}


5.Json工具类JsonUtil

package com.dstcar.common.utils.json;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.SimpleType;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author liu wei ping
 * @date 创建时间:2022年8月15日 下午6:18:33
 * @version 1.0
 */
@Slf4j
public class JsonUtil {

	public final static ObjectMapper mapper = new ObjectMapper();

	static {
		mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		// 多余的值不解析
		mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		// 解析器支持解析单引号
		mapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);
		// 解析器支持解析结束符
		mapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
		// mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
		mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
		// Include.NON_EMPTY 属性为 空(“”) 或者为 NULL 都不序列化
		// Include.NON_NULL 属性为NULL 不序列化
		mapper.setSerializationInclusion(Include.NON_EMPTY);

	}

	public static String toJson(Object obj) {
		String json = null;
		if (obj == null){
			return json;
		}
		try {
			json = mapper.writeValueAsString(obj);
		} catch (JsonProcessingException e) {
			log.error("转换JSON异常:{}",e);
		}
		return json;
	}

	/**
	 * 异常要抛出来
	 * 
	 * @param jsonStr
	 * @param objClass
	 * @param <T>
	 * @return
	 * @throws IOException
	 */
	public static <T> T json2Bean(String jsonStr, Class<T> objClass) throws IOException {
		if (jsonStr == null) {
			return null;
		}
		return mapper.readValue(jsonStr, objClass);
	}

	public static <T> T toBean(String jsonStr, Class<T> objClass) {
		if (jsonStr == null) {
			return null;
		}
		T t = null;
		try {
			t = mapper.readValue(jsonStr, objClass);
		} catch (Exception e) {
			log.error("objClass转换BEAN异常详情:"+jsonStr,e);
		}
		return t;
	}

	public static <T> T toBean(Reader reader, Type type) {
		T t = null;
		try {
			t = mapper.readValue(reader, mapper.constructType(type));
		} catch (Exception e) {
		}finally {
			try {
				if (reader != null){reader.close();}
			}catch (IOException e){}
		}
		return t;
	}

	public static <T> T toBean(InputStream in, Type type) {
		T t = null;
		try {
			t = mapper.readValue(in, mapper.constructType(type));

		} catch (Exception e) {
		} finally {
			try {
				if (in != null){in.close();}
			}catch (IOException e){}
		}
		return t;
	}

	private static <T> T toBean(String jsonStr, JavaType type) {
		if (jsonStr == null) {
			return null;
		}
		T t = null;
		try {
			t = mapper.readValue(jsonStr, type);
		} catch (Exception e) {
			log.error("JavaType转换BEAN异常详情:"+jsonStr,e);
		}
		return t;
	}

	public static <T> T toBean(String jsonStr, Class<?> c1, Class<?> c2, Class<?> c3) {
		return toBean(jsonStr, mapper.getTypeFactory().constructParametricType(c1,
				mapper.getTypeFactory().constructParametricType(c2, c3)));
	}

	public static <T> T toBean(String jsonStr, Class<?> collectionClass, Class<?>... elementClasses) {
		return toBean(jsonStr, mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses));
	}

	private static <T> T toCollection(String jsonStr, Class<?> collectionClass, Class<?>... elementClasses) {
		return toBean(jsonStr, mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses));
	}

	public static <T> T toSet(String jsonStr, Class<?>... elementClasses) {
		return toCollection(jsonStr, Set.class, elementClasses);
	}

	public static <T> T toList(String jsonStr, Class<?>... elementClasses) {
		return toCollection(jsonStr, List.class, elementClasses);
	}

	public static <T> List<T> toList(File file, Class<T> elementClasses) {

		try {
			return mapper.readValue(file, mapper.getTypeFactory()
           .constructParametricType(List.class, elementClasses));
		} catch (IOException e) {}
		return null;
	}

	public static <T> T toMap(String jsonStr) {
		return toBean(jsonStr, mapper.getTypeFactory()
        .constructMapType(Map.class, 
       String.class, Object.class));
	}

	public static <T> T toMap(String jsonStr, Class<?> c1, Class<?> c2, Class<?>... c3) {
		JavaType keyType = SimpleType.constructUnsafe(c1);
		JavaType valueType = mapper.getTypeFactory().constructParametricType(c2, c3);
		return toBean(jsonStr, mapper.getTypeFactory().constructMapType(Map.class, 
        keyType, valueType));
	}

	public static <T> T convertValue(Object obj,Class<T> clazz){
		return mapper.convertValue(obj,clazz);
	}

	public static <T> List<T> convertList(Object obj,Class<T> clazz){
		return 
        mapper.convertValue(obj,mapper.getTypeFactory()
        .constructCollectionType(List.class,clazz));
	}

	public static <K,V> Map<K,V> convertMap(Object obj,Class<K> kClazz,Class<V> vClazz){
		return mapper.convertValue(obj,mapper.getTypeFactory()
        .constructMapType(Map.class,kClazz,vClazz));
	}

	public static <T> T convertValue(Object obj,Type type){
		return mapper.convertValue(obj,mapper.constructType(type));
	}

	public static byte[] writeValueAsBytes(Object obj) throws JsonProcessingException {
		return mapper.writeValueAsBytes(obj);
	}

}

 6.防止重复提交异常

package com.dstcar.common.utils.lock;

/**
 * 防止重复提交异常
 * @author: liu wei ping
 * @date: 2022/08/15 20:32
 */
public class DistributedLockException extends RuntimeException {

    public DistributedLockException(String msg) {
        super(msg);
    }
}

7.在方法上加上@RedisLock注解

   	/**
	 * 盘点记录提交
	 * @param stockcountRecordSaveDto
	 * @return
	 */
	@Transactional
	@RedisLock(value = "'StockcountRecordOpenApiController_stockcountSubmit_'
     +#stockcountRecordSaveDto.stockcountRecord.id", quitRelease = false,expire = 30000)
	public boolean stockcountSubmit(StockcountRecordSaveDto stockcountRecordSaveDto, 
        SSOUser ssoUser) {
		log.info("stockcountSubmit.stockcountRecordSaveDto:{},ssoUser: 
        {}",toJson(stockcountRecordSaveDto),toJson(ssoUser));
		StopWatch stopWatch = new StopWatch();
		stopWatch.start("盘点记录提交开始:");
		StockcountRecord stockcountRecord = 
        stockcountRecordSaveDto.getStockcountRecord();
		Assert.notNull(stockcountRecord,"非法参数");
		//修改盘点记录状态
		updateStockcountRecord(stockcountRecordSaveDto,ssoUser);
        //生成盘点记录明细数据
		createStockcountRecordDetail(stockcountRecordSaveDto,ssoUser);
		//生成车辆图片上传
		uploadCarFile(stockcountRecordSaveDto,ssoUser);
        //生成盘点差异明细
		createStockcountRecordDiff(stockcountRecordSaveDto,ssoUser);
		//生成盘点车况信息和修改车辆属性值和记录车辆车况和属性变更日志
		createCarConditionInfo(stockcountRecordSaveDto,ssoUser);
		//修改盘点记录是否有差异
		updateStockcountRecordDiff(stockcountRecord);
		stopWatch.stop();
		log.info(stopWatch.prettyPrint());
		return true;
	}

8.效果如图:

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Redis通过自定义注解实现分布式锁。在代码中,我们可以看到使用了一个名为`@RedisLockAnnotation`的注解\[3\]。这个注解被用于标记需要加锁的方法。在注解的定义中,可以指定锁的类型和锁的过期时间。通过在切面中定义一个`@Pointcut`,来拦截使用了`@RedisLockAnnotation`注解的方法\[2\]。在拦截到这些方法后,会执行分布式锁的逻辑。具体的实现是通过Redisson来获取分布式锁对象,并使用`tryLock`方法来尝试获取锁\[1\]。如果成功获取到锁,就可以执行业务逻辑;如果获取锁失败,则需要等待或执行其他逻辑。这样,通过注解和切面的配合,我们可以方便地在需要的地方实现分布式锁的功能。 #### 引用[.reference_title] - *1* [尚品汇之通过自定义注解实现分布式锁](https://blog.csdn.net/weixin_65627952/article/details/128188867)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [使用注解实现REDIS分布式锁](https://blog.csdn.net/LinkSLA/article/details/130419280)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值