美文网首页
SpringBoot切面 + ThreadLocal 检查参数

SpringBoot切面 + ThreadLocal 检查参数

作者: 西5d | 来源:发表于2020-04-09 08:48 被阅读0次

    场景描述

    最近有个需求,要对某个通用参数做限制检查,判断是否存在,不存在判定为异常请求。最直接的方式是通过给每个接口加判断,如果接口少的情况下,是最简单有效的。但当前项目的情况比较复杂,这种方式会造成大量冗余代码,也不易维护和修改。大致说下现在的情况,项目中有上百的http接口,绝大多数接口是不关心这个通用参数的。 如果统一验证该参数,如在切面验证所有接口,可能导致一些正常请求无法完成。因此,就需要筛选出指定的接口,来添加对参数的验证。

    实现

    根据上面的描述,现在的目标是筛选出指定的接口,如果人工来操作,仍然是非常繁琐的。这里有个特点,就是要验证参数的服务接口,下游依赖服务接口只有三个,所以只要在下游依赖服务最终调用的地方,来进行验证。如果参数异常,直接返回失败结果,参数正常则再去调用下游服务,这样就比较简洁的实现了需要的功能。但是有个关键,怎么将要验证的参数带到下游依赖服务调用入口来判断?这里使用了ThreadLocal,在上游已经存在的统一AOP切片处写入,然后在最终调用处获取来判断。也就不用从Controller入口一直将参数传递下去。如下是实现的代码。

    代码详解

    //threadLocal 工具类,设置和获取变量值
    
    public class AppChannelUtils {
        private static final ThreadLocal<String> appChannelLocalThread = new ThreadLocal<>();
    
        public static String getAppChannel() {
            return appChannelLocalThread.get();
        }
    
        public static void setAppChannel(String channel) {
            appChannelLocalThread.set(channel);
        }
    
        public static boolean isBlankAndRemove() {
            boolean result = StringUtils.isBlank(getAppChannel());
            appChannelLocalThread.remove();
           //在使用后清理状态
            return result;
        }
    }
    
    //检查参数拦截器
    
    @Slf4j
    @Aspect
    @Component
    public class BeanAttributesCheckInterceptor implements Ordered {
        //设置返回msg
        private static final String MSG = "app channel is empty";
    
        //只拦截这三个最终调用的方法
        @Pointcut("execution(public * com.company.xxx.method3(*)) "
                + "|| execution(public * com.company.xxx.method2(*))"
                + "|| execution(public * com.company.xxx.method1(*))")
        private void pointcut() {
        }
    
        @Around(value = "pointcut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            //ThreadLocal中获取,进行验证。
            if (AppChannelUtils.isBlankAndRemove()) {
                String methodName = ((MethodSignature) joinPoint.getSignature()).getMethod().getName();
                log.error("check app channel error,params:{}", new Gson().toJson(joinPoint.getArgs()));
                //特殊方法的返回处理。
                //补充:可以根据反射方法获取返回值类型,提供拦截后的返回值
                if ("method1".equals(methodName)) {
                    CommonResp commonResp = new CommonResp();
                    commonResp.setMsg(MSG);
                    commonResp.setSuccess(false);
                    return commonResp;
                } else {
                    ActionResp resp = new ActionResp();
                    resp.setResult(-1);
                    resp.setMsg(MSG);
                    return resp;
                }
            } else {
                //参数正常,直接调用
                return joinPoint.proceed();
            }
        }
    
        //在前一个通用拦截器中设置参数值,所以顺序+1,保证在通用拦截器之后。
        @Override
        public int getOrder() {
            return InterceptorOrderEnum.ENHANCE_REQUEST_CONTEXT.getPriority() + 1;
        }
    }
    
    

    总结

    这样实现来说相对是比较简洁的。除了代码中提到的,可以根据反射获取到返回值的类型,还有两个问题值得关注。

    1. 上面的实现代码层面虽然简洁了,但是从测试的角度来说,还是非常繁琐和复杂的,需要全部覆盖测试用例。显而易见,最终只有三个接口,但是上游接口是非常多的。
    2. 还有就是如果某个阶段是异步调用,使用ThreadLocal的方式,应该是有问题的,因为整条链路不是在一个线程中执行,ThreadLocal也就没有意义。这种情况下,可以尝试用ConcurrentHashMap来保存上下文,完成衔接。
    3. 还有个问题,就是如果一个请求有多个涉及切面的方法执行。比如func1有多次重试的逻辑,isBlankAndRemove()方法就会有问题。因为前一个请求会清除对应数据。
    4. 如果换用Map来暂时存储参数,如果请求量增多,会导致Map容量过大,继而引起内存以及可能的线程资源竞争问题,这些都要注意。
      以上,就是本篇的全部内容,感谢阅读~

    相关文章

      网友评论

          本文标题:SpringBoot切面 + ThreadLocal 检查参数

          本文链接:https://www.haomeiwen.com/subject/tbhqmhtx.html