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

繁瑣的檢查

在平時的業務開發中,相信大家都有很多這樣的代碼:

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;
}

註解使用

@CheckXXXpublic class XXXController {    public void login(Parameter parameter) {      // 真正的邏輯代碼
    }
}

這樣,就能抽離出通用的邏輯,精簡通用的代碼。那麼,這個攔截器是什麼時候執行的呢?它的實現原理是什麼?

執行時機

通過查看自定義攔截器的UML類圖關係,可以看出來,其實是實現了HandlerInterceptor的preHandle方法,通過追蹤HandlerInterceptor的調用鏈路,最終是在請求進入分發器,執行doDispatch方法用的,而處理器是在初始化的時候就加載好。

整體的流程如下:

核心代碼:

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,面向切面編程。

  • 使用切面的優點:

    首先,現在每個關注點都集中於一個地 方,而不是分散到多處代碼中;其次,服務模塊更簡潔,因爲它們只包含主要關注點(或核 心功能)的代碼,而次要關注點的代碼被轉移到切面中了。

    ——摘自《Spring實戰》

關於AOP,網上有很多資料解釋,看維基百科的描述也很清晰,,筆者就不多贅述了。

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

總結

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

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

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

更多精彩內容,請關注個人公衆號。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章