繁瑣的檢查
在平時的業務開發中,相信大家都有很多這樣的代碼:
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,網上有很多資料解釋,看維基百科的描述也很清晰,,筆者就不多贅述了。
在這個例子裏面,每個接口的核心功能是響應爲業務功能提供服務,但是每個接口需要的參數檢查、安全檢查,都統一交給切面完成。如下圖所示:
總結
代碼和原理比較簡單,但是裏面包含的知識點卻不少,通過追朔源碼,能瞭解細節之餘,還能掌握某一類問題的實現方案。
原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。
如果本文對你有幫助,請點個贊吧,謝謝^_^
更多精彩內容,請關注個人公衆號。