使用攔截器統一處理通用檢查

繁瑣的檢查

在平時的業務開發中,相信你們都有不少這樣的代碼: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

在這個例子裏面,每一個接口的核心功能是響應爲業務功能提供服務,可是每一個接口須要的參數檢查、安全檢查,都統一交給切面完成。以下圖所示:

總結

代碼和原理比較簡單,可是裏面包含的知識點卻很多,經過追朔源碼,能瞭解細節之餘,還能掌握某一類問題的實現方案。

原創文章,文筆有限,才疏學淺,文中如有不正之處,萬望告知。

若是本文對你有幫助,請點個贊吧,謝謝^_^

更多精彩內容,請關注我的公衆號。

相關文章
相關標籤/搜索