過濾器和攔截器的區別
Spring開發中我們會遇到Filter過濾器與Interceptor攔截器的使用,他們都能對一些請求做一下預處理,但他們之間還是有很大的不同的:
- 攔截器是基於Java的反射機制的,而過濾器是基於函數回調。
- Filter是在Servlet規範中定義的,是Servlet容器支持的。而攔截器是在Spring容器內的,是Spring框架支持的。
- Filter在只在Servlet前後起作用。而攔截器能夠深入到方法前後、異常拋出前後等,因此攔截器的使用具有更大的彈性。所以在Spring構架的程序中,要優先使用攔截器)
- 攔截器可以訪問action上下文、值棧裏的對象,而過濾器不能訪問。
- 在action的生命週期中,攔截器可以多次被調用,而過濾器只能在容器初始化時被調用一次。
應用場景
- 日誌記錄:記錄請求信息的日誌,以便進行信息監控、信息統計、計算PV(Page View)等。
- 權限檢查:如登錄檢測,進入處理器檢測檢測是否登錄,如果沒有直接返回到登錄頁面;
- 攔截器能深入方法的前後執行過程,可以進行統計方法執行時間來判斷性能問題
過濾器
過濾器接口
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
Filter是javax.servlet中的接口,是有生命週期的,存在初始化,執行以及銷燬的過程。
過濾器的實現
下面來看一下我寫的一個權限檢驗的demo
public class AuthCheckFilter implements Filter {
//@Resource //不生效
private UserService userService;
//Filter的創建和銷燬由WEB服務器負責。 web 應用程序啓動時,
//web 服務器將創建Filter的實例對象,並調用其init方法進行初始化,
//容器只有在實例化過濾器時纔會調用該方法一次。
//容器爲這個方法傳遞一個FilterConfig對象,其中包含與Filter相關的配置信息
public void init(FilterConfig filterConfig) throws ServletException {
//獲取容器上下文,可以進行對Bean的操作
ServletContext context = filterConfig.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
userService = (UserServiceImpl)ctx.getBean(UserServiceImpl.class);
}
//每次filter進行攔截都會執行。需要注意的是過濾器的一個實例可以同時服務於多個請求,
// 特別需要注意線程同步問題,儘量不用或少用實例變量。
// 在過濾器的doFilter()方法實現中,任何出現在FilterChain的doFilter方法之前地方,
//request是可用的;在doFilter()方法之後response是可用的。
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
String user_token = request.getHeader("user_token");
String methodString=request.getMethod();
//這個判斷只是簡單的說明,通過init方法我們可以獲取bean對象來進行驗證操作
if(!userService.findOne(user_token)){
response.setContentType("application/json; charset=utf-8");
String jsonString="{\"status\":\"false\",\"info\":\"用戶未登陸或超時\"}";
OutputStream os = response.getOutputStream();
os.write(jsonString.getBytes());
os.close();
}
//這裏是個簡單的demo ,具體邏輯需要時再處理
if(StringUtils.isEmpty(user_token)){
response.setContentType("application/json; charset=utf-8");
String jsonString="{\"status\":\"false\",\"info\":\"用戶未登陸或超時\"}";
OutputStream os = response.getOutputStream();
os.write(jsonString.getBytes());
os.close();
}
filterChain.doFilter(servletRequest, servletResponse);
}
//在Web容器卸載 Filter 對象之前被調用
//如果過濾器使用了其他資源,需要在這個方法中釋放這些資源。
public void destroy() {
}
}
在上面代碼的初始化方法init()中,我添加了獲取Spring上下文以及bean對象的方法,這樣我們就可以有更大的靈活性來做處理。
Filter的配置是在web.xml中添加的
<filter>
<filter-name>authFilter</filter-name>
<filter-class>app.filter.AuthCheckFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>authFilter</filter-name>
<url-pattern>/user/*</url-pattern>
</filter-mapping>
<!--<filter-mapping>標記是有先後順序的,它的聲明順序說明容器是如何形成過濾器鏈的-->
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
過濾器的demo
https://github.com/lili1990/learning/tree/master/spring-learning/spring-filter
攔截器
攔截器接口
public interface HandlerInterceptor {
//預處理回調方法,實現處理器的預處理(如登錄檢查)
//第三個參數爲響應的處理器,攔截的controller對象
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;
}
攔截適配器
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
@Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
@Override
public void afterConcurrentHandlingStarted(
HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
}
}
這是HandlerInterceptor的適配器,簡單的實現了HandlerInterceptor(此處所以三個回調方法都是空實現,preHandle返回true。),我們這樣就不必要實現HandlerInterceptor的全部方法,我們通過HandlerInterceptorAdapter來實現自己需要的方法。
運行流程:
注意:異常流程中,比如是HandlerInterceptor2中斷的流程(preHandle返回false),此處僅調用它之前攔截器的preHandle返回true的afterCompletion方法。
攔截器配置
<mvc:annotation-driven />
<context:component-scan base-package="app.controller"/>
<context:component-scan base-package="app.service"/>
<!--攔截器配置-->
<mvc:interceptors>
<!-- 日誌攔截器,全局攔截 -->
<bean class="app.interceptor.LogInterceptor"/>
<!--配置攔截器, 多個攔截器,順序執行 -->
<!--權限校驗,排除部分請求-->
<mvc:interceptor>
<!-- 匹配的是url路徑, 如果不配置或/**,將攔截所有的Controller -->
<mvc:mapping path="/**"/>
<!--排除部分請求,不被攔截(下面攔截的請求都是swagger的請求)-->
<mvc:exclude-mapping path="/swagger-ui.html*"/>
<mvc:exclude-mapping path="/webjars/**"/>
<mvc:exclude-mapping path="/configuration/ui/**"/>
<mvc:exclude-mapping path="/swagger-resources/**"/>
<mvc:exclude-mapping path="/v2/**"/>
<bean class="app.interceptor.AuthCheckInterceptor" />
</mvc:interceptor>
<!-- 當設置多個攔截器時,先按順序調用preHandle方法,然後逆序調用每個攔截器的postHandle和afterCompletion方法 -->
</mvc:interceptors>
攔截器的demo
https://github.com/lili1990/learning/tree/master/spring-learning/spring-interceptor
攔截器的深入學習可以看Spring DispatcherServlet