使用拦截器统一处理通用检查

繁琐的检查

在平时的业务开发中,相信你们都有不少这样的代码:java

public void login(Parameter parameter) {
  if (!validateXXX(parameter)) {
    throw new BizException(ErrCode.PAMRM_ERROR);
  }
  
  // 真正的逻辑代码
}

那么,若是代码还有其余通用的校验,并且每加一个接口都要加这些校验逻辑,长此以往,代码会显得较臃肿,看起来会有不少重复的代码,那么有没有办法精简这部分代码呢?有!编程

Spring的HandlerInterceptor

先上代码数组

拦截器定义

public class CheckXXXHandlerInterceptor extends HandlerInterceptorAdapter {

    final Map<Method, Boolean> methodCache = new IdentityHashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HandlerMethod handlerMethod = (HandlerMethod) handler;

        /**
        这个是双重判断锁单例
        外层的判断,为了不在实例已经建立好的状况下再次加锁获取,影响性能;
        里层的判断,考虑在多线程环境下,多个线程同时过掉外层判断,也就是都已经判断变量为空,若是不加一重判断,仍是有可能重复建立。
        */
        Method method = handlerMethod.getMethod();
        if (!methodCache.containsKey(method)) {
            synchronized (methodCache) {
                if (!methodCache.containsKey(method)) {
                    boolean check = false;
                    if (method.isAnnotationPresent(CheckXXX.class)) {
                        check = method.getAnnotation(CheckXXX.class).value();
                    } else if (method.getDeclaringClass().isAnnotationPresent(CheckXXX.class)) {
                        check = method.getDeclaringClass().getAnnotation(CheckXXX.class).value();
                    }
                    methodCache.put(method, check);
                }
            }
        }
        if (methodCache.get(method)) {
            // do check
        }

        return true;
    }
}

注解定义

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface CheckXXX {
    boolean value() default true;
}

注解使用

@CheckXXX
public class XXXController {

    public void login(Parameter parameter) {
      // 真正的逻辑代码
    }
}

这样,就能抽离出通用的逻辑,精简通用的代码。那么,这个拦截器是何时执行的呢?它的实现原理是什么?安全

执行时机

经过查看自定义拦截器的UML类图关系,能够看出来,实际上是实现了HandlerInterceptor的preHandle方法,经过追踪HandlerInterceptor的调用链路,最终是在请求进入分发器,执行doDispatch方法用的,而处理器是在初始化的时候就加载好。多线程

总体的流程以下:app

核心代码:ide

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}


boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
    }
    return true;
}

拦截器数组interceptors是在Spring容器启动的时候初始化好的,实现原理比较简单,就是取出请求处理器的map,遍历调用注册好的拦截器。性能

实现原理

经过拦截器处理通用检查,背后的编程思想实际上是AOP,面向切面编程this

使用切面的优势:首先,如今每一个关注点都集中于一个地 方,而不是分散到多处代码中;其次,服务模块更简洁,由于它们只包含主要关注点(或核 心功能)的代码,而次要关注点的代码被转移到切面中了。----摘自《Spring实战》

关于AOP,网上有不少资料解释,看维基百科的描述也很清晰,,笔者就很少赘述了。spa

在这个例子里面,每一个接口的核心功能是响应为业务功能提供服务,可是每一个接口须要的参数检查、安全检查,都统一交给切面完成。以下图所示:

总结

代码和原理比较简单,可是里面包含的知识点却很多,经过追朔源码,能了解细节之余,还能掌握某一类问题的实现方案。

原创文章,文笔有限,才疏学浅,文中如有不正之处,万望告知。

若是本文对你有帮助,请点个赞吧,谢谢^_^

更多精彩内容,请关注我的公众号。

相关文章
相关标签/搜索