1. 程式人生 > 實用技巧 >Spring Boot 如何使用攔截器、過濾器、監聽器?

Spring Boot 如何使用攔截器、過濾器、監聽器?

過濾器

過濾器的英文名稱為Filter, 是Servlet技術中最實用的技術。

如同它的名字一樣,過濾器是處於客戶端和伺服器資原始檔之間的一道過濾網,幫助我們過濾掉一些不符合要求的請求,通常用作 Session 校驗,判斷使用者許可權,如果不符合設定條件,則會被攔截到特殊的地址或者基於特殊的響應。

過濾器的使用

首先需要實現Filter介面然後重寫它的三個方法

  • init 方法:在容器中建立當前過濾器的時候自動呼叫

  • destory 方法:在容器中銷燬當前過濾器的時候自動呼叫

  • doFilter 方法:過濾的具體操作

我們先引入Maven依賴,其中lombok是用來避免每個檔案建立 Logger 來列印日誌

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

我們首先實現介面,重寫三個方法,對包含我們要求的四個請求予以放行,將其它請求攔截重定向至/online

,只要在將MyFilter例項化後即可,我們在後面整合程式碼中一起給出。

importlombok.extern.log4j.Log4j2;
importorg.springframework.stereotype.Component;

importjavax.servlet.*;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjavax.servlet.http.HttpServletResponseWrapper;
importjava.io.IOException;

@Log4j2
publicclassMyFilterimplementsFilter{

@Override
publicvoidinit(FilterConfigfilterConfig)throwsServletException{
log.info("初始化過濾器");
}

@Override
publicvoiddoFilter(ServletRequestservletRequest,ServletResponseresponse,FilterChainfilterChain)throwsIOException,ServletException{
HttpServletRequestrequest=(HttpServletRequest)servletRequest;
HttpServletResponseWrapperwrapper=newHttpServletResponseWrapper((HttpServletResponse)response);
StringrequestUri=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
publicvoiddestroy(){
//在服務關閉時銷燬
log.info("銷燬過濾器");
}
}

攔截器

Java中的攔截器是動態攔截 action 呼叫的物件,然後提供了可以在 action 執行前後增加一些操作,也可以在 action 執行前停止操作,功能與過濾器類似,但是標準和實現方式不同。

  • 登入認證:在一些應用中,可能會通過攔截器來驗證使用者的登入狀態,如果沒有登入或者登入失敗,就會給使用者一個友好的提示或者返回登入頁面,當然大型專案中都不採用這種方式,都是調單點登入系統介面來驗證使用者。

  • 記錄系統日誌:我們在常見應用中,通常要記錄使用者的請求資訊,比如請求 ip,方法執行時間等,通過這些記錄可以監控系統的狀況,以便於對系統進行資訊監控、資訊統計、計算 PV、效能調優等。

  • 通用處理:在應用程式中可能存在所有方法都要返回的資訊,這是可以利用攔截器來實現,省去每個方法冗餘重複的程式碼實現。

使用攔截器

我們需要實現 HandlerInterceptor 類,並且重寫三個方法:

  • preHandle:在 Controoler 處理請求之前被呼叫,返回值是boolean型別,如果是true就進行下一步操作;若返回false,則證明不符合攔截條件,在失敗的時候不會包含任何響應,此時需要呼叫對應的response返回對應響應。

  • postHandler:在 Controoler 處理請求執行完成後、生成檢視前執行,可以通過ModelAndView對檢視進行處理,當然ModelAndView也可以設定為 null。

  • afterCompletion:在 DispatcherServlet 完全處理請求後被呼叫,通常用於記錄消耗時間,也可以對一些資源進行處理。

importlombok.extern.log4j.Log4j2;
importorg.springframework.stereotype.Component;
importorg.springframework.web.servlet.HandlerInterceptor;
importorg.springframework.web.servlet.ModelAndView;

importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjavax.servlet.http.HttpSession;

@Log4j2
@Component
publicclassMyInterceptorimplementsHandlerInterceptor{
@Override
publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{
log.info("【MyInterceptor】呼叫了:{}",request.getRequestURI());
request.setAttribute("requestTime",System.currentTimeMillis());
returntrue;
}

@Override
publicvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,
Objecthandler,ModelAndViewmodelAndView)throwsException{
if(!request.getRequestURI().contains("/online")){
HttpSessionsession=request.getSession();
StringsessionName=(String)session.getAttribute("name");
if("haixiang".equals(sessionName)){
log.info("【MyInterceptor】當前瀏覽器存在session:{}",sessionName);
}
}
}

@Override
publicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,
Objecthandler,Exceptionex)throwsException{
longduration=(System.currentTimeMillis()-(Long)request.getAttribute("requestTime"));
log.info("【MyInterceptor】[{}]呼叫耗時:{}ms",request.getRequestURI(),duration);
}
}

監聽器

監聽器通常用於監聽 Web 應用程式中物件的建立、銷燬等動作的傳送,同時對監聽的情況作出相應的處理,最常用於統計網站的線上人數、訪問量等。關注公眾號Java技術棧回覆boot可以獲取更多系列 Spring Boot 教程。

監聽器大概分為以下幾種:

  • ServletContextListener:用來監聽 ServletContext 屬性的操作,比如新增、修改、刪除。

  • HttpSessionListener:用來監聽 Web 應用種的 Session 物件,通常用於統計線上情況。

  • ServletRequestListener:用來監聽 Request 物件的屬性操作。

監聽器的使用

我們通過HttpSessionListener來統計當前線上人數、ip等資訊,為了避免併發問題我們使用原子int來計數。

ServletContext,是一個全域性的儲存資訊的空間,它的生命週期與Servlet容器也就是伺服器保持一致,伺服器關閉才銷燬。

request,一個使用者可有多個;

session,一個使用者一個;而servletContext,所有使用者共用一個。所以,為了節省空間,提高效率,ServletContext中,要放必須的、重要的、所有使用者需要共享的執行緒又是安全的一些資訊。

因此我們這裡用ServletContext來儲存線上人數sessionCount最為合適。

我們下面來統計當前線上人數:

importlombok.extern.log4j.Log4j2;

importjavax.servlet.http.HttpSessionEvent;
importjavax.servlet.http.HttpSessionListener;
importjava.util.concurrent.atomic.AtomicInteger;

@Log4j2
publicclassMyHttpSessionListenerimplementsHttpSessionListener{

publicstaticAtomicIntegeruserCount=newAtomicInteger(0);

@Override
publicsynchronizedvoidsessionCreated(HttpSessionEventse){
userCount.getAndIncrement();
se.getSession().getServletContext().setAttribute("sessionCount",userCount.get());
log.info("【線上人數】人數增加為:{}",userCount.get());

//此處可以在ServletContext域物件中為訪問量計數,然後傳入過濾器的銷燬方法
//在銷燬方法中呼叫資料庫入庫,因為過濾器生命週期與容器一致
}

@Override
publicsynchronizedvoidsessionDestroyed(HttpSessionEventse){
userCount.getAndDecrement();
se.getSession().getServletContext().setAttribute("sessionCount",userCount.get());
log.info("【線上人數】人數減少為:{}",userCount.get());
}
}

過濾器、攔截器、監聽器註冊

例項化三器

importcom.anqi.tool.sanqi.filter.MyFilter;
importcom.anqi.tool.sanqi.interceptor.MyInterceptor;
importcom.anqi.tool.sanqi.listener.MyHttpRequestListener;
importcom.anqi.tool.sanqi.listener.MyHttpSessionListener;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.boot.web.servlet.FilterRegistrationBean;
importorg.springframework.boot.web.servlet.ServletListenerRegistrationBean;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;
importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
publicclassWebConfigimplementsWebMvcConfigurer{
@Autowired
MyInterceptormyInterceptor;

@Override
publicvoidaddInterceptors(InterceptorRegistryregistry){
registry.addInterceptor(myInterceptor);
}

/**
*註冊過濾器
*@return
*/
@Bean
publicFilterRegistrationBeanfilterRegistrationBean(){
FilterRegistrationBeanfilterRegistration=newFilterRegistrationBean();
filterRegistration.setFilter(newMyFilter());
filterRegistration.addUrlPatterns("/*");
returnfilterRegistration;
}

/**
*註冊監聽器
*@return
*/
@Bean
publicServletListenerRegistrationBeanregistrationBean(){
ServletListenerRegistrationBeanregistrationBean=newServletListenerRegistrationBean();
registrationBean.setListener(newMyHttpRequestListener());
registrationBean.setListener(newMyHttpSessionListener());
returnregistrationBean;
}
}

測試

importcom.anqi.tool.sanqi.listener.MyHttpSessionListener;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.RestController;

importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpSession;

@RestController
publicclassTestController{

@GetMapping("addSession")
publicStringaddSession(HttpServletRequestrequest){
HttpSessionsession=request.getSession();
session.setAttribute("name","haixiang");
return"當前線上人數"+session.getServletContext().getAttribute("sessionCount")+"人";
}

@GetMapping("removeSession")
publicStringremoveSession(HttpServletRequestrequest){
HttpSessionsession=request.getSession();
session.invalidate();
return"當前線上人數"+session.getServletContext().getAttribute("sessionCount")+"人";
}

@GetMapping("online")
publicStringonline(){
return"當前線上人數"+MyHttpSessionListener.userCount.get()+"人";
}

}

以下是監聽請求的監聽器

importjavax.servlet.ServletRequestEvent;
importjavax.servlet.ServletRequestListener;
importjavax.servlet.http.HttpServletRequest;

publicclassMyHttpRequestListenerimplementsServletRequestListener{

@Override
publicvoidrequestDestroyed(ServletRequestEventsre){
System.out.println("request監聽器被銷燬");
}

@Override
publicvoidrequestInitialized(ServletRequestEventsre){
HttpServletRequestreq=(HttpServletRequest)sre.getServletRequest();
StringrequestURI=req.getRequestURI();
System.out.println(requestURI+"--"+"被呼叫");
}
}

攔截器與過濾器的區別

1.參考標準

  • 過濾器是 JavaEE 的標準,依賴於Servlet容器,生命週期也與容器一致,利用這一特性可以在銷燬時釋放資源或者資料入庫。

  • 攔截器是SpringMVC中的內容,依賴於web框架,通常用於驗證使用者許可權或者記錄日誌,但是這些功能也可以利用AOP來代替。

2.實現方式

  • 過濾器是基於回撥函式實現,無法注入ioc容器中的 bean。

  • 攔截器是基於反射來實現,因此攔截器中可以注入ioc容器中的 bean,例如注入 Redis 的業務層來驗證使用者是否已經登入。

本人免費整理了Java高階資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分散式等教程,一共30G,需要自己領取。
傳送門:https://shimo.im/docs/rQRKDPx6dPXqvYPq