Spring Boot 如何使用攔截器、過濾器、監(jiān)聽器?

過濾器
過濾器的英文名稱為 Filter, 是 Servlet 技術中最實用的技術。
如同它的名字一樣,過濾器是處于客戶端和服務器資源文件之間的一道過濾網,幫助我們過濾掉一些不符合要求的請求,通常用作 Session 校驗,判斷用戶權限,如果不符合設定條件,則會被攔截到特殊的地址或者基于特殊的響應。
過濾器的使用
首先需要實現 Filter接口然后重寫它的三個方法
init 方法:在容器中創(chuàng)建當前過濾器的時候自動調用
destory 方法:在容器中銷毀當前過濾器的時候自動調用
doFilter 方法:過濾的具體操作
我們先引入 Maven 依賴,其中 lombok 是用來避免每個文件創(chuàng)建 Logger 來打印日志
????org.projectlombok
????lombok
????org.springframework.boot
????spring-boot-starter-web
我們首先實現接口,重寫三個方法,對包含我們要求的四個請求予以放行,將其它請求攔截重定向至/online,只要在將MyFilter實例化后即可,我們在后面整合代碼中一起給出。
import?lombok.extern.log4j.Log4j2;
import?org.springframework.stereotype.Component;
import?javax.servlet.*;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;
import?javax.servlet.http.HttpServletResponseWrapper;
import?java.io.IOException;
@Log4j2
public?class?MyFilter?implements?Filter?{
????@Override
????public?void?init(FilterConfig?filterConfig)?throws?ServletException?{
????????log.info("初始化過濾器");
????}
??
????@Override
????public?void?doFilter(ServletRequest?servletRequest,?ServletResponse?response,?FilterChain?filterChain)?throws?IOException,?ServletException?{
????????HttpServletRequest?request?=?(HttpServletRequest)servletRequest;
????????HttpServletResponseWrapper?wrapper?=?new?HttpServletResponseWrapper((HttpServletResponse)?response);
????????String?requestUri?=?request.getRequestURI();
????????log.info("請求地址是:"+requestUri);
????????if?(requestUri.contains("/addSession")
????????????||?requestUri.contains("/removeSession")
????????????||?requestUri.contains("/online")
????????????||?requestUri.contains("/favicon.ico"))?{
????????????filterChain.doFilter(servletRequest,?response);
????????}?else?{
????????????wrapper.sendRedirect("/online");
????????}
????}
??
????@Override
????public?void?destroy()?{
????????//在服務關閉時銷毀
????????log.info("銷毀過濾器");
????}
}
攔截器
Java中的攔截器是動態(tài)攔截 action 調用的對象,然后提供了可以在 action 執(zhí)行前后增加一些操作,也可以在 action 執(zhí)行前停止操作,功能與過濾器類似,但是標準和實現方式不同。
登錄認證:在一些應用中,可能會通過攔截器來驗證用戶的登錄狀態(tài),如果沒有登錄或者登錄失敗,就會給用戶一個友好的提示或者返回登錄頁面,當然大型項目中都不采用這種方式,都是調單點登錄系統接口來驗證用戶。
記錄系統日志:我們在常見應用中,通常要記錄用戶的請求信息,比如請求 ip,方法執(zhí)行時間等,通過這些記錄可以監(jiān)控系統的狀況,以便于對系統進行信息監(jiān)控、信息統計、計算 PV、性能調優(yōu)等。
通用處理:在應用程序中可能存在所有方法都要返回的信息,這是可以利用攔截器來實現,省去每個方法冗余重復的代碼實現。
使用攔截器
我們需要實現 HandlerInterceptor 類,并且重寫三個方法:
preHandle:在 Controoler 處理請求之前被調用,返回值是
boolean類型,如果是true就進行下一步操作;若返回false,則證明不符合攔截條件,在失敗的時候不會包含任何響應,此時需要調用對應的response返回對應響應。postHandler:在 Controoler 處理請求執(zhí)行完成后、生成視圖前執(zhí)行,可以通過
ModelAndView對視圖進行處理,當然ModelAndView也可以設置為 null。afterCompletion:在 DispatcherServlet 完全處理請求后被調用,通常用于記錄消耗時間,也可以對一些資源進行處理。
import?lombok.extern.log4j.Log4j2;
import?org.springframework.stereotype.Component;
import?org.springframework.web.servlet.HandlerInterceptor;
import?org.springframework.web.servlet.ModelAndView;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;
import?javax.servlet.http.HttpSession;
@Log4j2
@Component
public?class?MyInterceptor?implements?HandlerInterceptor?{
????@Override
????public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)?throws?Exception?{
????????log.info("【MyInterceptor】調用了:{}",?request.getRequestURI());
????????request.setAttribute("requestTime",?System.currentTimeMillis());
????????return?true;
????}
????@Override
????public?void?postHandle(HttpServletRequest?request,?HttpServletResponse?response,
???????????????????????????Object?handler,?ModelAndView?modelAndView)?throws?Exception?{
????????if?(!request.getRequestURI().contains("/online"))?{
????????????HttpSession?session?=?request.getSession();
????????????String?sessionName?=?(String)?session.getAttribute("name");
????????????if?("haixiang".equals(sessionName))?{
????????????????log.info("【MyInterceptor】當前瀏覽器存在?session:{}",sessionName);
????????????}
????????}
????}
????@Override
????public?void?afterCompletion(HttpServletRequest?request,?HttpServletResponse?response,
????????????????????????????????Object?handler,?Exception?ex)?throws?Exception?{
????????long?duration?=?(System.currentTimeMillis()?-?(Long)request.getAttribute("requestTime"));
????????log.info("【MyInterceptor】[{}]調用耗時:{}ms",request.getRequestURI(),?duration);
????}
}
監(jiān)聽器
監(jiān)聽器通常用于監(jiān)聽 Web 應用程序中對象的創(chuàng)建、銷毀等動作的發(fā)送,同時對監(jiān)聽的情況作出相應的處理,最常用于統計網站的在線人數、訪問量等。關注公眾號Java技術?;貜蚥oot可以獲取更多系列 Spring Boot 教程。
監(jiān)聽器大概分為以下幾種:
ServletContextListener:用來監(jiān)聽 ServletContext 屬性的操作,比如新增、修改、刪除。
HttpSessionListener:用來監(jiān)聽 Web 應用種的 Session 對象,通常用于統計在線情況。
ServletRequestListener:用來監(jiān)聽 Request 對象的屬性操作。
監(jiān)聽器的使用
我們通過 HttpSessionListener來統計當前在線人數、ip等信息,為了避免并發(fā)問題我們使用原子int來計數。
ServletContext,是一個全局的儲存信息的空間,它的生命周期與Servlet容器也就是服務器保持一致,服務器關閉才銷毀。
request,一個用戶可有多個;
session,一個用戶一個;而servletContext,所有用戶共用一個。所以,為了節(jié)省空間,提高效率,ServletContext中,要放必須的、重要的、所有用戶需要共享的線程又是安全的一些信息。
因此我們這里用ServletContext來存儲在線人數sessionCount最為合適。
我們下面來統計當前在線人數:
import?lombok.extern.log4j.Log4j2;
import?javax.servlet.http.HttpSessionEvent;
import?javax.servlet.http.HttpSessionListener;
import?java.util.concurrent.atomic.AtomicInteger;
@Log4j2
public?class?MyHttpSessionListener?implements?HttpSessionListener?{
????public?static?AtomicInteger?userCount?=?new?AtomicInteger(0);
????@Override
????public?synchronized?void?sessionCreated(HttpSessionEvent?se)?{
????????userCount.getAndIncrement();
????????se.getSession().getServletContext().setAttribute("sessionCount",?userCount.get());
????????log.info("【在線人數】人數增加為:{}",userCount.get());
??????
????????//此處可以在ServletContext域對象中為訪問量計數,然后傳入過濾器的銷毀方法
????????//在銷毀方法中調用數據庫入庫,因為過濾器生命周期與容器一致
????}
????@Override
????public?synchronized?void?sessionDestroyed(HttpSessionEvent?se)?{
????????userCount.getAndDecrement();
????????se.getSession().getServletContext().setAttribute("sessionCount",?userCount.get());
????????log.info("【在線人數】人數減少為:{}",userCount.get());
????}
}
過濾器、攔截器、監(jiān)聽器注冊
實例化三器
import?com.anqi.tool.sanqi.filter.MyFilter;
import?com.anqi.tool.sanqi.interceptor.MyInterceptor;
import?com.anqi.tool.sanqi.listener.MyHttpRequestListener;
import?com.anqi.tool.sanqi.listener.MyHttpSessionListener;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.boot.web.servlet.FilterRegistrationBean;
import?org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.Configuration;
import?org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import?org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public?class?WebConfig?implements?WebMvcConfigurer?{
????@Autowired
????MyInterceptor?myInterceptor;
????@Override
????public?void?addInterceptors(InterceptorRegistry?registry)?{
????????registry.addInterceptor(myInterceptor);
????}
????/**
?????*?注冊過濾器
?????*?@return
?????*/
????@Bean
????public?FilterRegistrationBean?filterRegistrationBean(){
????????FilterRegistrationBean?filterRegistration?=?new?FilterRegistrationBean();
????????filterRegistration.setFilter(new?MyFilter());
????????filterRegistration.addUrlPatterns("/*");
????????return?filterRegistration;
????}
????/**
?????*?注冊監(jiān)聽器
?????*?@return
?????*/
????@Bean
????public?ServletListenerRegistrationBean?registrationBean(){
????????ServletListenerRegistrationBean?registrationBean?=?new?ServletListenerRegistrationBean();
????????registrationBean.setListener(new?MyHttpRequestListener());
????????registrationBean.setListener(new?MyHttpSessionListener());
????????return?registrationBean;
????}
}
測試
import?com.anqi.tool.sanqi.listener.MyHttpSessionListener;
import?org.springframework.web.bind.annotation.GetMapping;
import?org.springframework.web.bind.annotation.RestController;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpSession;
@RestController
public?class?TestController?{
????@GetMapping("addSession")
????public?String?addSession(HttpServletRequest?request)?{
????????HttpSession?session?=?request.getSession();
????????session.setAttribute("name",?"haixiang");
????????return?"當前在線人數"?+?session.getServletContext().getAttribute("sessionCount")?+?"人";
????}
????@GetMapping("removeSession")
????public?String?removeSession(HttpServletRequest?request)?{
????????HttpSession?session?=?request.getSession();
????????session.invalidate();
????????return?"當前在線人數"?+?session.getServletContext().getAttribute("sessionCount")?+?"人";
????}
????@GetMapping("online")
????public?String?online()?{
????????return?"當前在線人數"?+?MyHttpSessionListener.userCount.get()?+?"人";
????}
}
以下是監(jiān)聽請求的監(jiān)聽器
import?javax.servlet.ServletRequestEvent;
import?javax.servlet.ServletRequestListener;
import?javax.servlet.http.HttpServletRequest;
public?class?MyHttpRequestListener?implements?ServletRequestListener?{
????@Override
????public?void?requestDestroyed(ServletRequestEvent?sre)?{
????????System.out.println("request?監(jiān)聽器被銷毀");
????}
????@Override
????public?void?requestInitialized(ServletRequestEvent?sre)?{
????????HttpServletRequest?req?=?(HttpServletRequest)?sre.getServletRequest();
????????String?requestURI?=?req.getRequestURI();
????????System.out.println(requestURI+"--"+"被調用");
????}
}
攔截器與過濾器的區(qū)別
1.參考標準
過濾器是 JavaEE 的標準,依賴于 Servlet 容器,生命周期也與容器一致,利用這一特性可以在銷毀時釋放資源或者數據入庫。
攔截器是SpringMVC中的內容,依賴于web框架,通常用于驗證用戶權限或者記錄日志,但是這些功能也可以利用 AOP 來代替。
2.實現方式
過濾器是基于回調函數實現,無法注入 ioc 容器中的 bean。
攔截器是基于反射來實現,因此攔截器中可以注入 ioc 容器中的 bean,例如注入 Redis 的業(yè)務層來驗證用戶是否已經登錄。
程序汪資料鏈接
堪稱神級的Spring Boot手冊,從基礎入門到實戰(zhàn)進階
臥槽!字節(jié)跳動《算法中文手冊》火了,完整版 PDF 開放下載!
臥槽!阿里大佬總結的《圖解Java》火了,完整版PDF開放下載!
字節(jié)跳動總結的設計模式 PDF 火了,完整版開放下載!
歡迎添加程序汪個人微信 itwang009? 進粉絲群或圍觀朋友圈
