由于业务权限服务,相同数据可能存在多人操作的情况,所以存在共享资源操作竞争问题,如果针对于业务单独去控制,成本比较高,具体控制还要耦合于业务中,如果使用不规范,很容易造成竞争死锁问题,所以次处分享一个基于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) {
}