spring MVC——攔截器實現登入檢測和效能監控
1. 攔截器簡介
Spring MVC中的攔截器,類似於Servlet開發中的過濾器Filter,主要用來攔截使用者的請求並進行相應的處理,可以用來做日誌記錄、許可權驗證或者登陸檢測。
(1) 常見的應用場景
日誌記錄:記錄請求資訊的日誌,以便進行資訊監控、資訊統計、計算PV(Page View)等。
許可權檢查:如登入檢測,進入處理器檢測檢測是否登入,如果沒有直接返回到登入頁面;
效能監控:有時候系統在某段時間莫名其妙的慢,可以通過攔截器在進入處理器之前記錄開始時間,在處理完後記錄結束時間,從而得到該請求的處理時間;
通用行為:讀取cookie得到使用者資訊並將使用者物件放入請求,從而方便後續流程使用,只要是多個處理器都需要的即可使用攔截器實現。
OpenSessionInView:如Hibernate,在進入處理器開啟Session,在完成後關閉Session。
攔截器本質上也是AOP(面向切面程式設計),也就是說符合橫切關注點的所有功能都可以放入攔截器實現。
(2) 攔截器的介面
public interface HandlerInterceptor { boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; }
有3個方法,下面是這些方法的說明:
preHandle:預處理回撥方法,實現處理器的預處理(如登入檢查),第三個引數為響應的處理器(Controller);返回值:true表示繼續流程(如呼叫下一個攔截器或處理器);
false表示流程中斷(如登入檢查失敗),不會繼續呼叫其他的攔截器或處理器,此時我們需要通過response來產生響應;
postHandle:後處理回撥方法,實現處理器的後處理(但在渲染檢視之前),此時我們可以通過modelAndView(模型和檢視物件)對模型資料進行處理或對檢視進行處理,modelAndView也可能為null。
afterCompletion:整個請求處理完畢回撥方法,即在檢視渲染完畢時回撥,如效能監控中我們可以在此記錄結束時間並輸出消耗時間,還可以進行一些資源清理,類似於try-catch-finally中的finally,但僅呼叫處理器執行鏈中preHandle返回true的攔截器的afterCompletion。
(3) 攔截器介面卡
有時我們可能只需要實現三個回撥方法中的某一個,如果實現HandlerInterceptor介面的話,三個方法必須實現,不管你需不需要,此時spring提供了一個HandlerInterceptorAdapter介面卡(一種介面卡設計模式的實現),允許我們只實現需要的回撥方法。
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
public HandlerInterceptorAdapter() {
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
}
}
2、登入檢測
在訪問某些資源時,需要使用者登入後才能檢視,因此需要進行登入檢測。
流程:
1、訪問需要登入的資源時,由攔截器重定向到登入頁面;
2、如果訪問的是登入頁面,攔截器不應該攔截;
3、使用者登入成功後,往cookie/session新增登入成功的標識(如使用者編號);
4、下次請求時,攔截器通過判斷cookie/session中是否有該標識來決定繼續流程還是到登入頁面;
5、在此攔截器還應該允許遊客訪問的資源。
攔截器實現:
package com.etc.interceptor;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginCheckInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1、如果使用者已經登入 放行
if(request.getSession().getAttribute("user") != null){
return true;
}else {
//2、非法請求 即這些請求需要登入後才能訪問,重定向到登入頁面
response.sendRedirect(request.getContextPath()+"/login");
return false;
}
}
}
攔截器配置:
<mvc:interceptor>
<!-- 攔截所有的請求,這個必須寫在前面,也就是寫在【不攔截】的上面 -->
<mvc:mapping path="/**"/>
<!-- 但是排除下面這些,也就是不攔截請求 -->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/logout"/>
<mvc:exclude-mapping path="/doLogin" />
<bean class="com.etc.interceptor.LoginCheckInterceptor"/>
</mvc:interceptor>
3、效能監控
如何記錄請求的處理時間,得到一些慢請求(如處理時間超過500毫秒),從而進行效能改進,我們來看如何使用攔截器來實現。
實現分析:
1.在進入處理器之前記錄開始時間,即在攔截器的preHandle記錄開始時間;
2.在結束請求處理之後記錄結束時間,即在攔截器的afterCompletion記錄結束實現,並用結束時間-開始時間得到這次請求的處理時間。
問題:
我們的攔截器是單例,因此不管使用者請求多少次都只有一個攔截器實現,即執行緒不安全,那我們應該怎麼記錄時間呢?
解決方案:
使用ThreadLocal,它是執行緒繫結的變數,提供執行緒區域性變數(一個執行緒一個ThreadLocal,A執行緒的ThreadLocal只能看到A執行緒的ThreadLocal,不能看到B執行緒的ThreadLocal)。
Spring提供的一個命名的ThreadLocal實現(NamedThreadLocal)。
攔截器實現:
package com.etc.interceptor;
import org.springframework.core.NamedThreadLocal;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class PerformanceMonitorInterceptor extends HandlerInterceptorAdapter{
private NamedThreadLocal<Long> threadLocal = new NamedThreadLocal<>("PerformanceMonitor");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//請求開始時間
long startTime = System.currentTimeMillis();
//執行緒繫結變數(該資料只有當前請求的執行緒可見)
threadLocal.set(startTime);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//請求結束時間
long endTime = System.currentTimeMillis();
//獲取執行緒繫結的區域性變數(開始時間)
long startTime = threadLocal.get();
long elapse = endTime - startTime;
//為了測試只要大於0就記錄,一般設定為一個比較大的數,比如300,代表300毫秒
if(elapse > 0){
System.out.println(String.format("%s elapse %d 毫秒", request.getRequestURI(), elapse));
}
}
}
攔截器配置:
在配置時將該攔截器放在攔截器鏈的第一個,這樣得到的時間才是比較準確。
<mvc:interceptor>
<!-- 攔截所有的請求,這個必須寫在前面,也就是寫在【不攔截】的上面 -->
<mvc:mapping path="/**"/>
<!-- 但是排除下面這些,也就是不攔截請求 -->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/logout"/>
<mvc:exclude-mapping path="/doLogin" />
<bean class="com.etc.interceptor.PerformanceMonitorInterceptor"/>
</mvc:interceptor>
測試:
4. Spring MVC中的攔截器和Servlet中的過濾器的區別
(1) 攔截器是基於Java的反射機制的,而過濾器是JavaEE標準,基於函式回撥
(2) 攔截器不依賴於Servlet容器,過濾器依賴於Servlet容器。
(3) 攔截器可以獲取IOC容器中的各個bean,而過濾器就不行,在攔截器裡注入一個Service,可以呼叫業務邏輯。
5. 總結
推薦能使用Servlet規範中的過濾器Filter實現的功能就用Filter實現,因為HandlerInteceptor只有在Spring MVC環境下才能使用,因此Filter是最通用的、最先應該使用的。比如如登入檢測攔截器最好使用Filter來實現。