AOP 自定义注解
我是一只java后端小菜鸡,有什么不对的地方请指正....
在我们的开发中肯定有一些情况需要对有些特别的端口进行权限校验,或者日志输出的情况。
后者日志输出的情况相比前一种权限校验的粒度大一点,所以可以直接使用AOP中的表达式即可完成对controller下面的所有端口进行日志输出。
但是面对一些细粒度的校验,比如说这个controller中有一些需要进行权限校验,有一些不需要?那么这个时候我们应该如何操作呢?
答:可以使用自定义注解,实现的效果就是你只需要在一个你想要做限制的端口上面加上一个注解就可以做到权限校验,其他什么都不需要做
那怎么实现呢?
第一步:我们需要自己创建一个注解
第二步:对这个注解进行 AOP 操作(可以把他当成你之前写的切点表达式)
第一步
那么我们应该如何自定义一个注解呢?
创建注解,我们只需要点击如下的操作即可
但是在此次的AOP自定义注解中我们需要给注解加上俩个注解来确保它的使用(没错就是给我们自定义注解使用的注解)
就是如下俩个
@Target(?)
@Retention(?)
上面第一个顾名思义目标目标是什么?我们需要对什么进行操作?
很明显我们是要对一些方法进行操作那么就是 ElementType.METHOD
这个Target注解里面还有很多的类型这里就不一一举例了,可以自行按住ctrl+鼠标左键 点击进入查看
上面第二个我们要确认,这个注解是在什么时候运行的,是要永远便随这这个代码编译出来的吗,还是知识看着做个标记(例如 @Override)?
也是很明显,这次我们的是一个需要伴随着主代码最后编译出来的所以我们选择如下注解:RetentionPolicy.RUNTIME
注解使用范围:
RetentionPolicy.SOURCE:只保留在源文件中
RetentionPolicy.CLASS:会被编译成class,但是jvm运行时候会无视它(默认使用)
RetentionPolicy.RUNTIME:好像正常的代码一样
第二步
接下来我们就要使用日常的一些注解让这个注解能够使用起来,创建一个普通的类(上面的是我们的自定义注解类,相当于我们后的切点)标注上如下注解,就像平时的注解使用类一样
@Aspect
@Component
那么如何让我们的方法能够解析到这个切点呢?
如下
@Around("@annotation(interfaceInfo)")
public Object doInterceptor(ProceedingJoinPoint joinPoint, InterfaceApiCheck interfaceInfo) throws Throwable {
将其像这样放入即可,只不过切点表达式被我们改成了@annotation然后加上 我们的 interfaceInfo ,主要看下面方法入参中的InterfaceApiCheck interfaceInfo,这个就是我们就是自己定义的注解类(上面的interfaceInfo就是为了传给这个入参的),那么就可以看出这个就像是一个有属性(可能也没有属性,前提是你在构造这个注解的时候有没有默认给属性,和你在使用这个注解的时候有没有传属性)的对象了。
举例:假如一个注解在使用的时候给注解加了如下这个属性
/**
* 必须有某个角色
*
* @return
*/
String mustRole() default "";
使用的时候
@InterfaceApiCheck(mustRole = "admin")
这个时候我们的interfaceInfo里面就有一个入参是里面的mustRole是admin了,我们就可以用其做一些权限校验
最后一段是我个人对于限流AOP的相关代码
@Aspect
@Component
@Slf4j
public class ApiInterceptor {
@Resource
private UserService userService;
@Resource
private InterfaceInfoService interfaceInfoService;
@Resource
private UserInterfaceInfoService userInterfaceInfoService;
@Resource
private HttpServletRequest httpServletRequest;
private HashMap<String,RateLimiter> cacheMap = new HashMap<>();
/**
* 执行拦截
*
* @param joinPoint
* @param
* @return
*/
@Around("@annotation(interfaceInfo)")
public Object doInterceptor(ProceedingJoinPoint joinPoint, InterfaceApiCheck interfaceInfo) throws Throwable {
//Nginx配置了相关获取权限
String ip = httpServletRequest.getHeader("X-Real-IP");
log.info("调用者ip----->" + ip);
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature)signature;
//执行的方法
String methodName = joinPoint.getTarget().getClass().getName() + "." + methodSignature.getName();
String recordKey = ip + "->" + methodName;
//如果存在不存在放入map
if(!cacheMap.containsKey(recordKey)){
cacheMap.put(recordKey,RateLimiter.create(1,1, TimeUnit.SECONDS));
}
//一个ip一个方法对应一个限流器 获取对应的限流器
RateLimiter rateLimiter = cacheMap.get(recordKey);
//每1秒生成一个
if(!rateLimiter.tryAcquire()){
throw new BusinessException(ErrorCode.FORBIDDEN_RATELIMITER,"请求过于频繁");
}
Object[] args = joinPoint.getArgs();
Object arg = args[0];
InterfaceInvokeRequest interfaceInvokeRequest = null;
if(arg instanceof InterfaceInvokeRequest){
interfaceInvokeRequest = (InterfaceInvokeRequest)arg;
}
if (interfaceInvokeRequest == null || interfaceInvokeRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR,"接口不存在");
}
// 参数校验
long id = interfaceInvokeRequest.getId();
// 判断是否存在
InterfaceInfo InterfaceInfo = interfaceInfoService.getById(id);
if (InterfaceInfo == null) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR);
}
if(InterfaceInfo.getStatus() != InterfaceStateEnum.ONLINE.getValue()){
throw new BusinessException(ErrorCode.PARAMS_ERROR,"接口已经下线");
}
//获取当前登录用户
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
User loginUser = userService.getLoginUser(request);
Long userId = loginUser.getId();
//查看接口调用次数
QueryWrapper<UserInterfaceInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userId",userId);
queryWrapper.eq("interfaceInfoId",id);
UserInterfaceInfo userInterfaceInfo = userInterfaceInfoService.getOne(queryWrapper);
if(userInterfaceInfo == null){
throw new BusinessException(ErrorCode.PARAMS_ERROR,"该接口暂不可用");
}
Integer leftNum = userInterfaceInfo.getLeftNum();
if(leftNum <= 0){
throw new BusinessException(ErrorCode.PARAMS_ERROR,"接口无使用次数");
}
return joinPoint.proceed();
}
}
小技巧
在AOP(不仅仅可以在AOP)处获取request(不确定分布式能不能用)
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();