分布式锁AOP实现

分布式锁

分布式锁AOP实现

添加注解类
package cn.rc100.common.security.annotation;

import java.lang.annotation.*;

/**
 * @author xxx
 * @title: RedisLock
 * @description: TODO
 */
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {

    /**
     * 加锁业务键
     */
    String lockName();

    /**
     * 加锁业务键后缀
     */
    String suffix() default "";

    /**
     * 获得锁名时拼接前后缀用到的分隔符
     */
    String separator() default "_";

    /**
     * <pre>
     *     获取注解的方法参数列表的某个参数对象的某个属性值来作为lockName。因为有时候lockName是不固定的。
     *     当param不为空时,可以通过argNum参数来设置具体是参数列表的第几个参数,不设置则默认取第一个。
     * </pre>
     */
    String param() default "";

    /**
     * 将方法第argNum个参数作为锁
     */
    int argNum() default 0;

    /**
     * 尝试获取锁的等待时间(单位:毫秒)
     * <p>
     * 在等待时间内获取锁成功,返回true
     * 等待时间结束了,还没有获取到锁那么返回false
     * 默认300s
     * </p>
     */
    String waitTime() default "300000";

    /**
     * 分布式锁的租约时间(单位:毫秒)
     * <p>
     * 如果“不想”使用分布式锁的无限续租,则“指定”锁的租约时间
     * 如果“想要”使用分布式锁的无限续租,则“无需指定”锁的租约时间(耗时比较久的业务)
     * 默认使用无限续租
     * </p>
     */
    String leaseTime() default "-1";

    /**
     * 《预留》
     * 是否支持降级(默认不支持)
     * <p>
     * 这里的降级指在`Redis`宕机时,不使用分布式锁,直接调用方法,尽可能保证服务内部业务逻辑的可用性(AP)
     * 考虑并发场景下,是否会造成数据的不一致/脏数据问题,假如业务上需要确保一致性,则应该支持降级,否则不应该支持降级操作(CP)
     * </p>
     */
    boolean degrade() default false;

    /**
     * 是否忽略加锁失败的异常(默认不忽略)
     */
    boolean ignoreFail() default false;

}

Redisson客户端
package cn.rc100.common.security.annotation;

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import javax.annotation.PostConstruct;

/**
 * @author xxx
 * @title: RedissonClientConfig
 * @description: TODO
 */
@Configuration
@AllArgsConstructor
public class RedissonClientConfig {

    @ApiModelProperty(value = "域名")
    private String host;

    @ApiModelProperty(value = "密码")
    private String pwd;

    @ApiModelProperty(value = "端口")
    private String port;

    private Environment environment;

    @Autowired
    public RedissonClientConfig(Environment environment) {
        this.environment = environment;
    }

    @PostConstruct
    public void init() {
        host = environment.getProperty("spring.redis.host");
        pwd = environment.getProperty("spring.redis.password");
        port = environment.getProperty("spring.redis.port");
    }

    @Bean
    public RedissonClient getRedisson() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(pwd);
        return Redisson.create(config);
    }
}

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.6.1</version>
</dependency>
切面
package cn.rc100.common.security.annotation;

import cn.hutool.core.util.ReflectUtil;
import jodd.util.StringUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author xxx
 * @title: RedisLockAspect
 * @description: TODO
 */
@Aspect
@Slf4j
@Component
@SuppressWarnings({"checkstyle:magicnumber"})
@AllArgsConstructor
public class RedisLockAspect {
    private final RedissonClient redissonClient;

    @Around("@annotation(cn.rc100.common.security.annotation.RedisLock) && @annotation(redisLock)")
    public Object aroundLock(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext ctx = new StandardEvaluationContext();
        ctx.setVariable("arguments", joinPoint.getArgs());

        //所在类
        Object[] arguments = joinPoint.getArgs();
        final String key = getLockName(redisLock, arguments);

        //String key = getLockName(parser, ctx, methodName, redisLock);
        Long waitTime = Long.valueOf(!redisLock.waitTime().contains("arguments") ? redisLock.waitTime()
                : parser.parseExpression(redisLock.waitTime()).getValue(ctx, String.class));
        Long renewTime = Long.valueOf(!redisLock.leaseTime().contains("arguments") ? redisLock.leaseTime()
                : parser.parseExpression(redisLock.leaseTime()).getValue(ctx, String.class));
        RLock rLock = null;
        Boolean isLocked = Boolean.FALSE;
        try {
            rLock = redissonClient.getLock(key);
            if (Objects.isNull(rLock)) {
                //降级规则
                //if(redisLock.degrade()){
                //    return degradeHandler(joinPoint, key);
                //}
                throw new RuntimeException(String.format("RLock 获取失败, key:%s", key));
            }
            isLocked = tryLock(rLock, key, waitTime, renewTime);
            if (isLocked) {
                log.info(String.format("成功获取 RedisLock, key: %s ", key));
                return joinPoint.proceed();
            }
            throw new RuntimeException(String.format("RedisLock 获取失败, key: %s", key));
        } catch (RuntimeException e) {
            if (redisLock.ignoreFail()) {
                return null;
            }
            throw new RuntimeException();
        } catch (Throwable throwable) {
            log.error(String.format("获取 RedisLock 后业务逻辑执行失败, key: %s, error: %s",
                    key, throwable));
            return null;
        } finally {
            unlock(isLocked, rLock, key);
        }
    }

    private Boolean tryLock(RLock rLock, String key, Long waitTime, Long leaseTime) {
        try {
            return leaseTime != -1L ? rLock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS)
                    : rLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            log.error(String.format("加 RedisLock 异常, key:%s, e:%s", key, e));
            throw new RuntimeException();
        }
    }

    private void unlock(Boolean isLocked, RLock rLock, String key) {
        if (isLocked && rLock.isHeldByCurrentThread()) {
            rLock.unlock();
            log.debug(String.format("释放 RedisLock 成功, key: %s", key));
        }
    }

    public String getLockName(RedisLock redisLock, Object[] args) {
        try {
            StringBuilder lName = new StringBuilder();
            String lockName = redisLock.lockName(),
                    suffix = redisLock.suffix(),
                    separator = redisLock.separator(),
                    param = redisLock.param();
            if (StringUtil.isNotEmpty(lockName)) {
                lName.append(lockName);
            }
            if (args.length > 0) {
                if (StringUtil.isNotEmpty(param)) {
                    Object arg;
                    if (redisLock.argNum() > 0) {
                        arg = args[redisLock.argNum() - 1];
                    } else {
                        arg = args[0];
                    }
                    lName.append(separator).append(String.valueOf(getParam(arg, param)));
                } else if (redisLock.argNum() > 0) {
                    lName.append(separator).append(args[redisLock.argNum() - 1].toString());
                }
            }
            if (StringUtil.isNotEmpty(suffix)) {
                lName.append(separator).append(suffix);
            }
            lockName = lName.toString();
            return lockName;
        } catch (Exception e) {
            throw new IllegalArgumentException("Can't get or generate lockName accurately!");
        }

    }

    /**
     * 从方法参数获取数据
     *
     * @param param
     * @param arg   方法的参数数组
     * @return
     */
    public Object getParam(Object arg, String param) {
        if (StringUtil.isNotEmpty(param) && arg != null) {
            try {
                Object result = ReflectUtil.getFieldValue(arg, param);
                return result;
            } catch (Exception e) {
                throw new RuntimeException("", e);
            }
        }
        return null;
    }
    
}


使用示例1:
 @RedisLock(lockName = "financialReceipt", param = "financialId", suffix = ".lock")
    public R insert(FinancialReceiptRecordDTO receiptRecordDTO) {
        //synchronized (this) {
        Integer financialId = receiptRecordDTO.getFinancialId();
        Financial financial = financialService
                .getOne(Wrappers.<Financial>lambdaQuery().eq(Financial::getId, financialId)
                        .eq(Financial::getDelFlag, false));
        if (financial == null) {
            return R.failed("未找到当前财务信息!");
        }
        //应收金额
        BigDecimal receivable = financial.getReceivable() == null ? new BigDecimal(0) : financial.getReceivable();
        //当前账务信息的:全部实收金额
        BigDecimal netReceiptsTotal = financial.getNetReceipts() != null ?
                financial.getNetReceipts().add(receiptRecordDTO.getNetReceipts()) : receiptRecordDTO
                .getNetReceipts();
        if (receivable.compareTo(netReceiptsTotal) == -1) {
            return R.failed("总共实收金额已超出应收金额!");
        }
        List<ContractFileDTO> fileDTOList = receiptRecordDTO.getFileDTOList();
        boolean flag = false;
        if (fileDTOList != null && fileDTOList.size() > 0) {
            //附件有无
            flag = true;
        }
        EplusUser user = SecurityUtils.getUser();
        receiptRecordDTO.setCreateDept(user.getDeptId());
        receiptRecordDTO.setCreateUser(user.getId());
        receiptRecordDTO.setCreateTime(LocalDateTime.now());
        Random random = new Random(System.currentTimeMillis());
        receiptRecordDTO.setVoucherNo(Math.abs(random.nextLong()));
        receiptRecordDTO.setAnyAttachment(flag ? 1 : 0);
        //保存实收记录
        save(receiptRecordDTO);
        //保存附件
        contractFileService.saveFile(fileDTOList, receiptRecordDTO.getId());
        //修改当前账务信息
        financial.setUpdateTime(LocalDateTime.now());
        financial.setNetReceipts(netReceiptsTotal);
        financial.setCollectionTime(LocalDateTime.now());
        financial.setOperatorUser(user.getId());
        financial.setAnyAttachment(flag ? 1 : 0);
        financial.setCollectionTime(receiptRecordDTO.getCreateTime());
        financial.setCollectionAccount(receiptRecordDTO.getCollectionAccount());
        return R.ok(financialService.updateById(financial));
        //}
    }
使用示例2:
@RedisLock(argNum = 1, lockNamePost = ".lock")
    public Integer aspect(String i) {
        RMap<String, Integer> map = redissonClient.getMap("redisLockTest");
        Integer count = map.get("count");
        if (count > 0) {
            count = count - 1;
            map.put("count", count);
        }
        return count;
    }
使用示例3:
    @RedisLock(lockName = "stock",  waitTime = "200000")
    public void insert(Integer id) {
        Stock stock = service.getById(id);
        Integer num = stock.getNum();
        --num;
        if (num < 0) {
            return;
        }
        System.out.println("num=" + num);
        stock.setNum(num);
        stock.updateById();
    }
   
    @RedisLock(lockName = "lock", expire = "#arguments[1]")
    public Long createXxxWithLock(String key, Long sleepTime, String commLog){
  
    }
     
参考:
https://www.cnblogs.com/sprinkle/p/8366414.html
https://qimok.cn/1117.html
posted @ 2022-01-22 15:28  随风笔记  阅读(303)  评论(0)    收藏  举报