自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Resubmission {
long expire() default 3000;
String prefix() default "";
String lockKey() default "";
}
定义redis锁相关接口和类
public interface DistributedLock {
long TIMEOUT_MILLIS = 30000;
int RETRY_TIMES = 2;
long SLEEP_MILLIS = 500;
boolean lock(String key);boolean lock(String key, int retryTimes, long sleepMillis);
boolean lock(String key, long expire);
boolean lock(String key, long expire, int retryTimes);
boolean lock(String key, long expire, int retryTimes, long sleepMillis);
boolean releaseLock(String key);
}
public abstract class AbstractDistributedLock implements DistributedLock {
@Override
public boolean lock(String key) {
return lock(key , TIMEOUT_MILLIS, RETRY_TIMES, SLEEP_MILLIS);
}
@Override
public boolean lock(String key, int retryTimes, long sleepMillis) {
return lock(key, TIMEOUT_MILLIS, retryTimes, sleepMillis);
}
@Override
public boolean lock(String key, long expire) {
return lock(key, expire, RETRY_TIMES, SLEEP_MILLIS);
}
@Override
public boolean lock(String key, long expire, int retryTimes) {
return lock(key, expire, retryTimes, SLEEP_MILLIS);
}
}
@Component
public class RedisDistributedLock extends AbstractDistributedLock {
Logger log = LoggerFactory.getLogger(RedisDistributedLock.class);
@Autowired
@Resource
private RedisTemplate<Object, Object> redisTemplate;
private ThreadLocal<String> lockFlag = new ThreadLocal<String>();
public static final String UNLOCK_LUA;
static {
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append(" return redis.call(\"del\",KEYS[1]) ");
sb.append("else ");
sb.append(" return 0 ");
sb.append("end ");
UNLOCK_LUA = sb.toString();
}
public RedisDistributedLock() {
super();
}
@Override
public boolean lock(String key, long expire, int retryTimes, long sleepMillis) {
boolean result = setRedis(key, expire);
while ((!result) && retryTimes-- > 0) {
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
return false;
}
result = setRedis(key, expire);
}
return result;
}
private boolean setRedis(final String key, final long expire) {
try {
String uuid = UUID.randomUUID().toString();
lockFlag.set(uuid);
return redisTemplate.opsForValue().setIfAbsent(key,uuid,expire, TimeUnit.MILLISECONDS);
} catch (Exception e) {
log.info("redis lock error.", e);
}
return false;
}
@Override
public boolean releaseLock(String key) {
try {
DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<Boolean>(UNLOCK_LUA,Boolean.class);
return redisTemplate.execute(defaultRedisScript, Arrays.asList(key),lockFlag.get());
} catch (Exception e) {
log.error("release lock occured an exception", e);
} finally {
lockFlag.remove();
}
return false;
}
}
定义AOP拦截
@Aspect
@Component
public class ResubmissionAspect {
@Autowired
private RedisDistributedLock distributedLock;
@Pointcut("@annotation(com.neway.wisdommuck.frame.annotation.Resubmission)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
String methodName = method.getName();
Resubmission resubmission = method.getAnnotation(Resubmission.class);
String lockKey = resubmission.lockKey();
String prefix = resubmission.prefix();
Object elExpression = getByEl(lockKey, point);
String redisKey = prefix + "_" + methodName + "_" + elExpression;
boolean lock = distributedLock.lock(redisKey, resubmission.expire());
if (!lock) {
throw new CommonException("操作太频繁,请稍后再试");
}
Object proceed = null;
try {
proceed = point.proceed();
} catch (Throwable e) {
distributedLock.releaseLock(redisKey);
if (e instanceof CommonException) {
throw new CommonException(e.getMessage());
}
throw new RuntimeException(e);
}
distributedLock.releaseLock(redisKey);
return proceed;
}
private Object getByEl(String el, ProceedingJoinPoint point) {
if (!isEl(el)) {
return el;
}
Method method = ((MethodSignature) point.getSignature()).getMethod();
String[] paramNames = getParameterNames(method);
Object[] arguments = point.getArgs();
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(el);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < arguments.length; i++) {
context.setVariable(paramNames[i], arguments[i]);
}
return expression.getValue(context, Object.class);
}
private String[] getParameterNames(Method method) {
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
return u.getParameterNames(method);
}
private boolean isEl(String str) {
return str.contains("#");
}
}
使用方式
@Resubmission(prefix = "pre", lockKey = "#user.id")
@Override
public R add(User user) {
return R.ok();
}