Spring Boot 中使用過(guò)濾器、攔截器和監(jiān)聽(tīng)器

過(guò)濾器
過(guò)濾器的英文名稱(chēng)為?Filter, 是?Servlet?技術(shù)中最實(shí)用的技術(shù)。
如同它的名字一樣,過(guò)濾器是處于客戶(hù)端和服務(wù)器資源文件之間的一道過(guò)濾網(wǎng),幫助我們過(guò)濾掉一些不符合要求的請(qǐng)求,通常用作 Session 校驗(yàn),判斷用戶(hù)權(quán)限,如果不符合設(shè)定條件,則會(huì)被攔截到特殊的地址或者基于特殊的響應(yīng)。
過(guò)濾器的使用
首先需要實(shí)現(xiàn)?Filter接口然后重寫(xiě)它的三個(gè)方法
init 方法:在容器中創(chuàng)建當(dāng)前過(guò)濾器的時(shí)候自動(dòng)調(diào)用
destory 方法:在容器中銷(xiāo)毀當(dāng)前過(guò)濾器的時(shí)候自動(dòng)調(diào)用
doFilter 方法:過(guò)濾的具體操作
我們先引入?Maven?依賴(lài),其中?lombok?是用來(lái)避免每個(gè)文件創(chuàng)建 Logger 來(lái)打印日志
????org.projectlombok
????lombok
????org.springframework.boot
????spring-boot-starter-web
我們首先實(shí)現(xiàn)接口,重寫(xiě)三個(gè)方法,對(duì)包含我們要求的四個(gè)請(qǐng)求予以放行,將其它請(qǐng)求攔截重定向至/online,只要在將MyFilter實(shí)例化后即可,我們?cè)诤竺嬲洗a中一起給出。
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("初始化過(guò)濾器");
????}
??
????@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("請(qǐng)求地址是:"+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()?{
????????//在服務(wù)關(guān)閉時(shí)銷(xiāo)毀
????????log.info("銷(xiāo)毀過(guò)濾器");
????}
}
攔截器
Java中的攔截器是動(dòng)態(tài)攔截 action 調(diào)用的對(duì)象,然后提供了可以在 action 執(zhí)行前后增加一些操作,也可以在 action 執(zhí)行前停止操作,功能與過(guò)濾器類(lèi)似,但是標(biāo)準(zhǔn)和實(shí)現(xiàn)方式不同。
登錄認(rèn)證:在一些應(yīng)用中,可能會(huì)通過(guò)攔截器來(lái)驗(yàn)證用戶(hù)的登錄狀態(tài),如果沒(méi)有登錄或者登錄失敗,就會(huì)給用戶(hù)一個(gè)友好的提示或者返回登錄頁(yè)面,當(dāng)然大型項(xiàng)目中都不采用這種方式,都是調(diào)單點(diǎn)登錄系統(tǒng)接口來(lái)驗(yàn)證用戶(hù)。
記錄系統(tǒng)日志:我們?cè)诔R?jiàn)應(yīng)用中,通常要記錄用戶(hù)的請(qǐng)求信息,比如請(qǐng)求 ip,方法執(zhí)行時(shí)間等,通過(guò)這些記錄可以監(jiān)控系統(tǒng)的狀況,以便于對(duì)系統(tǒng)進(jìn)行信息監(jiān)控、信息統(tǒng)計(jì)、計(jì)算 PV、性能調(diào)優(yōu)等。
通用處理:在應(yīng)用程序中可能存在所有方法都要返回的信息,這是可以利用攔截器來(lái)實(shí)現(xiàn),省去每個(gè)方法冗余重復(fù)的代碼實(shí)現(xiàn)。
使用攔截器
我們需要實(shí)現(xiàn) HandlerInterceptor 類(lèi),并且重寫(xiě)三個(gè)方法:
preHandle:在 Controoler 處理請(qǐng)求之前被調(diào)用,返回值是?
boolean類(lèi)型,如果是true就進(jìn)行下一步操作;若返回false,則證明不符合攔截條件,在失敗的時(shí)候不會(huì)包含任何響應(yīng),此時(shí)需要調(diào)用對(duì)應(yīng)的response返回對(duì)應(yīng)響應(yīng)。postHandler:在 Controoler 處理請(qǐng)求執(zhí)行完成后、生成視圖前執(zhí)行,可以通過(guò)
ModelAndView對(duì)視圖進(jìn)行處理,當(dāng)然ModelAndView也可以設(shè)置為 null。afterCompletion:在 DispatcherServlet 完全處理請(qǐng)求后被調(diào)用,通常用于記錄消耗時(shí)間,也可以對(duì)一些資源進(jìn)行處理。
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】調(diào)用了:{}",?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】當(dāng)前瀏覽器存在?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】[{}]調(diào)用耗時(shí):{}ms",request.getRequestURI(),?duration);
????}
}
監(jiān)聽(tīng)器
監(jiān)聽(tīng)器通常用于監(jiān)聽(tīng) Web 應(yīng)用程序中對(duì)象的創(chuàng)建、銷(xiāo)毀等動(dòng)作的發(fā)送,同時(shí)對(duì)監(jiān)聽(tīng)的情況作出相應(yīng)的處理,最常用于統(tǒng)計(jì)網(wǎng)站的在線(xiàn)人數(shù)、訪(fǎng)問(wèn)量等。
監(jiān)聽(tīng)器大概分為以下幾種:
ServletContextListener:用來(lái)監(jiān)聽(tīng) ServletContext 屬性的操作,比如新增、修改、刪除。
HttpSessionListener:用來(lái)監(jiān)聽(tīng) Web 應(yīng)用中的 Session 對(duì)象,通常用于統(tǒng)計(jì)在線(xiàn)情況。
ServletRequestListener:用來(lái)監(jiān)聽(tīng) Request 對(duì)象的屬性操作。
監(jiān)聽(tīng)器的使用
我們通過(guò)?HttpSessionListener來(lái)統(tǒng)計(jì)當(dāng)前在線(xiàn)人數(shù)、ip等信息,為了避免并發(fā)問(wèn)題我們使用原子int來(lái)計(jì)數(shù)。
ServletContext,是一個(gè)全局的儲(chǔ)存信息的空間,它的生命周期與Servlet容器也就是服務(wù)器保持一致,服務(wù)器關(guān)閉才銷(xiāo)毀。
request,一個(gè)用戶(hù)可有多個(gè);
session,一個(gè)用戶(hù)一個(gè);而servletContext,所有用戶(hù)共用一個(gè)。所以,為了節(jié)省空間,提高效率,ServletContext中,要放必須的、重要的、所有用戶(hù)需要共享的線(xiàn)程又是安全的一些信息。
因此我們這里用ServletContext來(lái)存儲(chǔ)在線(xiàn)人數(shù)sessionCount最為合適。
我們下面來(lái)統(tǒng)計(jì)當(dāng)前在線(xiàn)人數(shù):
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("【在線(xiàn)人數(shù)】人數(shù)增加為:{}",userCount.get());
??????
????????//此處可以在ServletContext域?qū)ο笾袨樵L(fǎng)問(wèn)量計(jì)數(shù),然后傳入過(guò)濾器的銷(xiāo)毀方法
????????//在銷(xiāo)毀方法中調(diào)用數(shù)據(jù)庫(kù)入庫(kù),因?yàn)檫^(guò)濾器生命周期與容器一致
????}
????@Override
????public?synchronized?void?sessionDestroyed(HttpSessionEvent?se)?{
????????userCount.getAndDecrement();
????????se.getSession().getServletContext().setAttribute("sessionCount",?userCount.get());
????????log.info("【在線(xiàn)人數(shù)】人數(shù)減少為:{}",userCount.get());
????}
}
過(guò)濾器、攔截器、監(jiān)聽(tīng)器注冊(cè)
實(shí)例化三器
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);
????}
????/**
?????*?注冊(cè)過(guò)濾器
?????*?@return
?????*/
????@Bean
????public?FilterRegistrationBean?filterRegistrationBean(){
????????FilterRegistrationBean?filterRegistration?=?new?FilterRegistrationBean();
????????filterRegistration.setFilter(new?MyFilter());
????????filterRegistration.addUrlPatterns("/*");
????????return?filterRegistration;
????}
????/**
?????*?注冊(cè)監(jiān)聽(tīng)器
?????*?@return
?????*/
????@Bean
????public?ServletListenerRegistrationBean?registrationBean(){
????????ServletListenerRegistrationBean?registrationBean?=?new?ServletListenerRegistrationBean();
????????registrationBean.setListener(new?MyHttpRequestListener());
????????registrationBean.setListener(new?MyHttpSessionListener());
????????return?registrationBean;
????}
}
測(cè)試
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?"當(dāng)前在線(xiàn)人數(shù)"?+?session.getServletContext().getAttribute("sessionCount")?+?"人";
????}
????@GetMapping("removeSession")
????public?String?removeSession(HttpServletRequest?request)?{
????????HttpSession?session?=?request.getSession();
????????session.invalidate();
????????return?"當(dāng)前在線(xiàn)人數(shù)"?+?session.getServletContext().getAttribute("sessionCount")?+?"人";
????}
????@GetMapping("online")
????public?String?online()?{
????????return?"當(dāng)前在線(xiàn)人數(shù)"?+?MyHttpSessionListener.userCount.get()?+?"人";
????}
}
以下是監(jiān)聽(tīng)請(qǐng)求的監(jiān)聽(tīng)器
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)聽(tīng)器被銷(xiāo)毀");
????}
????@Override
????public?void?requestInitialized(ServletRequestEvent?sre)?{
????????HttpServletRequest?req?=?(HttpServletRequest)?sre.getServletRequest();
????????String?requestURI?=?req.getRequestURI();
????????System.out.println(requestURI+"--"+"被調(diào)用");
????}
}
攔截器與過(guò)濾器的區(qū)別
1.參考標(biāo)準(zhǔn)
過(guò)濾器是 JavaEE 的標(biāo)準(zhǔn),依賴(lài)于?Servlet?容器,生命周期也與容器一致,利用這一特性可以在銷(xiāo)毀時(shí)釋放資源或者數(shù)據(jù)入庫(kù)。
攔截器是SpringMVC中的內(nèi)容,依賴(lài)于web框架,通常用于驗(yàn)證用戶(hù)權(quán)限或者記錄日志,但是這些功能也可以利用?AOP?來(lái)代替。
2.實(shí)現(xiàn)方式
過(guò)濾器是基于回調(diào)函數(shù)實(shí)現(xiàn),無(wú)法注入?ioc?容器中的 bean。
攔截器是基于反射來(lái)實(shí)現(xiàn),因此攔截器中可以注入?ioc?容器中的 bean,例如注入 Redis 的業(yè)務(wù)層來(lái)驗(yàn)證用戶(hù)是否已經(jīng)登錄。
