Spring分布式锁的starter封装(Redisson)

由于业务权限服务,相同数据可能存在多人操作的情况,所以存在共享资源操作竞争问题,如果针对于业务单独去控制,成本比较高,具体控制还要耦合于业务中,如果使用不规范,很容易造成竞争死锁问题,所以次处分享一个基于AOP的分布式锁的start。欢迎一起探讨,对于增强改进的地方欢迎提供建议。
此处利用aop的MethodInterceptor进行方法级别的拦截,并且配合注解使用完成整个流程。

一、主要注解

1.启动Redission配置的注解Enable注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({RedissonConfiguration.class, LockMethodHelper.class})
public @interface EnableRedissonLock {

}

2.方法上是否启动锁

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLock {

    String fieldKey();
}

3.key拼接注解:

@Documented
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface KeyParam {
    /**
     * 如果动态参数在command对象中,那么就需要设置columns的值为command对象中的属性名可以为多个,否则不需要设置该值
     * <p>例1:public void test(@KeyParam({"id"}) MemberCommand member)
     * <p>例2:public void test(@KeyParam({"id","loginName"}) MemberCommand member)
     * <p>例3:public void test(@KeyParam String memberId)
     */
    String[] columns() default {};

    /**
     * 获取锁等待时间
     *
     * @return 等待时间
     */
    long waitTime() default 1L;

    /**
     * 锁释放时间
     *
     * @return 自动释放时间
     */
    long leaseTime() default 5L;

}

二、Redisssion的配置

1、基本配置参数封装

@Setter
@Getter
@Configuration
@ConfigurationProperties(prefix = "spring.redisson")
public class RedissonProperties {

    /**
     * redis访问地址
     */
    private String address;
    /**
     * 配置模式
     */
    private String connectModel;
    /**
     * 密码
     */
    private String password;
    /**
     * 超时时间
     */
    private int timeout;
    /**
     *  连接池大小
     */
    private int connectionPoolSize;
    /**
     * Minimum idle Redis connection amount.
     */
    private int connectionMinimumIdleSize;
}

2、配置执行封装可用的RedissonClient(目前单机模式):

@Configuration
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonConfiguration {

    @Bean
    @ConditionalOnMissingBean(RedissonProperties.class)
    @ConditionalOnProperty(prefix = "spring.redisson", value = {"address", "password"})
    RedissonClient redissonSingleClient(RedissonProperties redissonProperties) {
        Config config = new Config();
        config.useSingleServer()
            .setAddress(redissonProperties.getAddress())
            .setTimeout(redissonProperties.getTimeout())
            .setConnectionPoolSize(redissonProperties.getConnectionPoolSize())
            .setConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumIdleSize())
            .setPassword(redissonProperties.getPassword());
        return Redisson.create(config);
    }


}

三、目标方法拦:

@Slf4j
public class LockMethodHelper implements MethodInterceptor {


    public static final String BEFORE_KEY = "LOCK:";

    @Autowired(required = false)
    private RedissonClient redissonClient;

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Method method = methodInvocation.getMethod();
        Object[] args = methodInvocation.getArguments();
        //锁key
        String key = builderKey(method, args);
        // key不存在的情况下,不加锁处理,直接执行目标方法
        if (StringUtils.isEmpty(key)) {
            return methodInvocation.proceed();
        }
        RLock lock = redissonClient.getLock(key);
        try {
            if (lock.tryLock()) {
                // 通过反射机制调用目标方法
                return methodInvocation.proceed();
            } else {
                log.warn("资源竞争太大了,获取锁失败了,key为:{}", key);
                throw new LockException("人太多了,系统小哥已经处理不过来了...");
            }
        } catch (Exception e) {
            log.error("执行目标方法抛出异常,异常信息为:{}", e);
            throw e;
        } finally {
            lock.unlock();
        }
    }

    private String builderKey(Method method, Object[] args) throws NoSuchFieldException, IllegalAccessException {
        NeedLock needLock = method.getAnnotation(NeedLock.class);
        if (null == needLock) {
            return null;
        }
        // 锁的前半部分key
        String key = BEFORE_KEY + needLock.fieldKey();
        // 迭代全部参数的注解,根据使用KeyParam的注解的参数所在的下标,来获取args中对应下标的参数值拼接到前半部分key上
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                // 当前参数的注解不包含keyparam
                if (!annotation.annotationType().getClass().isInstance(KeyParam.class)) {
                    continue;
                }
                // 当前参数的注解包含keyparam,获取注解配置的值
                String[] columns = ((KeyParam) annotation).columns();
                if (columns.length == 0) {
                    // 普通数据类型直接拼接
                    if (null == args[i]) {
                        log.error("动态参数不能为null!");
                        throw new RuntimeException("动态参数不能为null!");
                    }
                    key += args[i];
                } else {
                    // keyparam的columns值不为null,所以当前参数应该是对象类型
                    for (int j = 0; j < columns.length; j++) {
                        Class<? extends Object> clasz = args[i].getClass();
                        Field declaredField = clasz.getDeclaredField(columns[j]);
                        declaredField.setAccessible(true);
                        Object value = declaredField.get(args[i]);
                        key += value;
                    }
                }
            }
        }
        return key;
    }
}

四、使用案例demo:

1、拦截范围配置,使用方自行配置拦截包范围

@EnableRedissonLock
@Configuration
public class DisturiteLockConfig {

    public static final String traceExecution = "execution(* com.workstation.biz.service..*.*(..))";

    @Autowired
    LockMethodHelper lockMethodHelper;


    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(traceExecution);
        // 配置增强类advisor
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
        advisor.setPointcut(pointcut);
        advisor.setAdvice(lockMethodHelper);
        return advisor;
    }
}

2、方法上使用:

   // 基于对象的使用
    @NeedLock(fieldKey = "user:uuid")
    public void userChange(@KeyParam(columns = {"uuid"}) UserDTO user) {

    }
// 基于字段的使用
   @NeedLock(fieldKey = "user:uuid")
    public void userChange(@KeyParam String uuid) {

    }

在这里插入图片描述

发布了41 篇原创文章 · 获赞 14 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Yunwei_Zheng/article/details/104986463
今日推荐