springboot下使用攔截器和過濾器
1. 攔截器Interceptor
Spring MVC的攔截器(Interceptor)和Filter不同,但是也可以實現對請求進行預處理,後處理。先介紹它的使用,只需要兩步:
1.1 實現攔截器
實現攔截器可以自定義實現HandlerInterceptor介面,也可以通過繼承HandlerInterceptorAdapter類,後者是前者的實現類。如果preHandle方法return true,則繼續後續處理。
public class InterceptorDemo implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
StringBuffer requestURL = httpServletRequest.getRequestURL();
System.out.println("前置攔截器1 preHandle: 請求的uri為:"+requestURL.toString());
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("攔截器1 postHandle: ");
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("攔截器1 afterCompletion: ");
}
}
1.2 註冊攔截器
實現攔截器後還需要將攔截器註冊到spring容器中,可以通過繼承WebMvcConfigurerAdapter類,覆蓋其addInterceptors(InterceptorRegistry registry)方法。記得把Bean註冊到Spring容器中,可以選擇@Component 或者 @Configuration。
@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry){
InterceptorRegistration registration2 = registry.addInterceptor(new InterceptorDemo2());
//配置攔截路徑
registration2.addPathPatterns("/**");
//配置不攔截的路徑
registration2.excludePathPatterns("/**.html");
//註冊其他的攔截器,執行順序和配置順序有關係
//註冊攔截器
InterceptorRegistration registration = registry.addInterceptor(new InterceptorDemo());
//配置攔截路徑
registration.addPathPatterns("/**");
//配置不攔截的路徑
registration.excludePathPatterns("/**.html");
}
注意這裡註冊了兩個攔截器。這兩個攔截器的執行順序和配置順序有關係,即先配置順序就在前(感覺這樣不太方便,但沒有找到設定類似order的API)。
發起一個請求,在控制檯可以看到攔截器生效:
前置攔截器2 preHandle: 使用者名稱:null
前置攔截器1 preHandle: 請求的uri為:http://localhost:8010/user/353434
攔截器1 postHandle:
攔截器2 postHandle:
攔截器1 afterCompletion:
攔截器2 afterCompletion:
1.3 攔截器的總結
1.3.1 工作原理
一個攔截器,只有preHandle方法返回true,postHandle、afterCompletion才有可能被執行;如果preHandle方法返回false,則該攔截器的postHandle、afterCompletion必然不會被執行。攔截器不是Filter,卻實現了Filter的功能,其原理在於:
- 所有的攔截器(Interceptor)和處理器(Handler)都註冊在HandlerMapping中。
- Spring MVC中所有的請求都是由DispatcherServlet分發的。
- 當請求進入DispatcherServlet.doDispatch()時候,首先會得到處理該請求的Handler(即Controller中對應的方法)以及所有攔截該請求的攔截器。攔截器就是在這裡被呼叫開始工作的。
1.3.2 攔截器工作流程
正常流程:
1.Interceptor2.preHandle 2.Interceptor1.preHandle 3.Controller處理請求 4.Interceptor1.postHandle 5.Interceptor2.postHandle 6.渲染檢視view 2.Interceptor1.afterCompletion 2.Interceptor2.afterCompletion
中斷流程
如果在Interceptor1.preHandle中報錯或返回false ,那麼接下來的流程就會被中斷,但注意被執行過的攔截器的afterCompletion仍然會執行。下圖為Interceptor1.preHandle返回false的情況:前置攔截器2 preHandle: 使用者名稱:null 前置攔截器1 preHandle: 請求的uri為:http://localhost:8010/user/353434 攔截器2 afterCompletion:
1.3.3 和Filter共存時的執行順序
攔截器是在DispatcherServlet這個servlet中執行的,因此所有的請求最先進入Filter,最後離開Filter。其順序如下。
Filter->Interceptor.preHandle->Handler->Interceptor.postHandle->Interceptor.afterCompletion->Filter
1.3.4 應用場景
攔截器本質上是面向切面程式設計(AOP),符合橫切關注點的功能都可以放在攔截器中來實現,主要的應用場景包括:
- 登入驗證,判斷使用者是否登入。
- 許可權驗證,判斷使用者是否有許可權訪問資源。
- 日誌記錄,記錄請求日誌,以便統計請求訪問量。
- 處理cookie、本地化、國際化、主題等。
- 效能監控,監控請求處理時長等。
2. 過濾器Filter
springboot下過濾器的使用有兩種形式:
2.1 註解形式
建立一個Filter,並使用WebFilter註解進行修飾,表示該類是一個Filter,以便於啟動類進行掃描的時候確認
@WebFilter(urlPatterns = "/*",filterName = "filter2")
public class FilterAnnotationTest implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("過濾器2開始初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("過濾器2開始工作");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("過濾器2銷燬");
}
}
然後在啟動類上添加註解@ServletComponentScan,該註解用於自動掃描指定包下(預設是與啟動類同包下)的WebFilter/WebServlet/WebListener等特殊類。
2.2 程式碼註冊方式
同樣編寫Filter,但是不在新增WebFilter註解,
public class FilterPorcess implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("過濾器開始初始化。。。");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
System.out.println("過濾器開始工作。。"+httpServletRequest.getRequestURL());
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
System.out.println("過濾器開始銷燬");
}
}
然後利用filterRegistrationBean來進行註冊。
@Configuration
public class FilterDemo {
@Bean
@Order(2)
//spring boot 會按照order值的大小,從小到大的順序來依次過濾
public FilterRegistrationBean configFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new FilterPorcess());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setName("sessionFilter");
return filterRegistrationBean;
}
}
2.3 過濾器filter和攔截器Interceptor的區別
spring的攔截器和servlet的過濾器有相似之處,都是AOP思想的體現,都可以實現許可權檢查,日誌記錄,不同的是
1. 適用範圍不同:Filter是Servlet容器規定的,只能使用在servlet容器中,而攔截器的使用範圍就大得多
2. 使用的資源不同:攔截器是屬於spring的一個元件,因此可以使用spring的所有資源,物件,如service物件,資料來源,事務控制等,而過濾器就不行
3. 深度不同:Filter還在servlet前後起作用。而攔截器能夠深入到方法前後,異常丟擲前後,因此攔截器具有更大的彈性,所有在spring框架中應該優先使用攔截器。
通過除錯可以發現,攔截器的執行過程是在過濾器的doFilter中執行的,過濾器的初始化會在專案啟動時執行。
過濾器開始工作。。http://localhost:8010/user/353434
前置攔截器2 preHandle: 使用者名稱:null
前置攔截器1 preHandle: 請求的uri為:http://localhost:8010/user/353434
攔截器1 postHandle:
攔截器2 postHandle:
攔截器1 afterCompletion:
攔截器2 afterCompletion:
過濾器開始工作。。http://localhost:8010/favicon.ico
可以通過這個部落格裡的一張圖來說明:
3. 監聽器
監聽器的簡單使用如下:先編寫監聽器的實現:
@WebListener
public class WebListenerDemo implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("監聽器初始化。。。。。。。。。。。。");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("監聽器銷燬。。。。。。。。。。。");
}
}
監聽session建立的監聽器(可以用來統計線上人數)
@WebListener
public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("。。。建立session成功");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("。。。銷燬session");
}
}
然後在啟動類上添加註解@ServletComponentScan即可,當然也可以註冊到spring容器中省卻@ServletComponentScan註解。
@Configuration
public class ListenerConfig {
@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean() {
ServletListenerRegistrationBean slrBean = new ServletListenerRegistrationBean();
slrBean.setListener(new WebListenerDemo());
return slrBean;
}
@Bean
public ServletListenerRegistrationBean sessionListenerRegistrationBean() {
ServletListenerRegistrationBean slrBean = new ServletListenerRegistrationBean();
slrBean.setListener(new SessionListener());
return slrBean;
}
}