在很多实际场景中,需要给接口加上挡板和分布式锁。比如,最常见的定时任务,有时候需要通过配置决定这个定时任务是该运行还是该暂停,有时候开启了多个实例,但是在同一时刻只允许其中一个实例运行这个定时任务(防止相同数据被执行多次),这时候有一种解决方案就是给这个定时任务加上分布式锁。
因为这些场景在项目中很常见,所以不推荐把挡板和分布式锁的判断逻辑硬编码到业务逻辑中。所以希望通过一个接口级的注解达到这种效果,这样既减少了编码工作量,又统一了挡板和分布式锁的实现方法,减少出错的可能性。下面介绍利用Spring的AOP特性,给接口加上挡板和分布式锁。
首先要定义一个注解,这个注解命名为@Baffle。@Baffle注解属于方法级别,有三个属性:
package com.leaforbook.common.annotation;
import java.lang.annotation.*;
@Documented
@Target({ ElementType.METHOD , ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Baffle {
String allowed() default "Y";
String lock() default "";
String name() default "";
}
下面是切面逻辑的实现,首先要指定切点——加了@Baffle注解的方法都是切点。然后在around方法中根据注解属性,实现挡板和分布式锁的逻辑。最后在after方法实现释放锁的逻辑。
切面逻辑实现以后,只要加上@Baffle注解,就可以自带挡板和分布式锁功能了。是不是非常方便?
package com.leaforbook.common.aspect;
import com.github.pagehelper.util.StringUtil;
import com.leaforbook.common.annotation.Baffle;
import com.leaforbook.common.util.DistributedLock;
import com.leaforbook.common.util.SnowFlake;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class BaffleAspect {
@Autowired
private Environment env;
@Autowired
private DistributedLock distributedLock;
@Pointcut("@annotation(baffle)")
public void bafflePointcut(Baffle baffle) {
}
@Before("bafflePointcut(baffle)")
public void before(JoinPoint joinPoint, Baffle baffle) {
}
@Around("bafflePointcut(baffle)")
public Object around(ProceedingJoinPoint joinPoint, Baffle baffle) throws Throwable {
log.info(baffle.allowed()+" "+baffle.lock()+" before");
String allowed = baffle.allowed();
if(allowed.startsWith("${")&&allowed.endsWith("}")) {
allowed = env.getProperty(allowed.substring(2,allowed.length()-1));
}
if(!"Y".equals(allowed)) {
log.info("该方法已设置挡板:"+baffle.name());
return null;
}
String lock = baffle.lock();
if(lock.startsWith("${")&&lock.endsWith("}")) {
lock = env.getProperty(lock.substring(2,lock.length()-1));
}
if(StringUtil.isNotEmpty(lock)) {
boolean isSuccess = distributedLock.tryLock(lock);
if(!isSuccess) {
log.info("该方法已被其他线程锁定:"+baffle.name());
return null;
}
}
return joinPoint.proceed();
}
@After("bafflePointcut(baffle)")
public void after(JoinPoint joinPoint, Baffle baffle) {
log.info(baffle.allowed()+" "+baffle.lock()+" after");
String lock = baffle.lock();
if(lock.startsWith("${")&&lock.endsWith("}")) {
lock = env.getProperty(lock.substring(2,lock.length()-1));
}
if(StringUtil.isNotEmpty(lock)) {
distributedLock.unLock(lock);
}
}
}
BaffleAspect有两个成员变量:
下面是DistributedLock接口的内容:
package com.leaforbook.common.util;
import org.springframework.stereotype.Component;
@Component
public interface DistributedLock {
boolean tryLock(String key);
void unLock(String key);
}
具体实现你可以根据自己的情况去实现。有人用Redis实现,有人用Zookeeper实现,有人用关系型数据库实现……超出本文范畴,不做介绍。
实质上,本文就是一种自定义方法级注解的实现过程。通过本文介绍的方法,可以实现很多有用的方法级注解,比如服务注册,日志记录……大家可以发挥自己的想象力,根据自己的业务场景,实现自定义注解。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。