SpringBoot之攔截器與過濾器解讀
SpringBoot 攔截器 過濾器
1、過濾器和攔截器觸發(fā)時機不一樣,過濾器是在請求進入容器后,但請求進入servlet之前進行預(yù)處理的。請求結(jié)束返回也是,是在servlet處理完后,返回給前端之前。
2、攔截器可以獲取IOC容器中的各個bean,而過濾器就不行,因為攔截器是spring提供并管理的,spring的功能可以被攔截器使用,在攔截器里注入一個service,可以調(diào)用業(yè)務(wù)邏輯。而過濾器是JavaEE標準,只需依賴servlet api ,不需要依賴spring。
3、過濾器的實現(xiàn)基于回調(diào)函數(shù)。而攔截器(代理模式)的實現(xiàn)基于反射
4、Filter是依賴于Servlet容器,屬于Servlet規(guī)范的一部分,而攔截器則是獨立存在的,可以在任何情況下使用。
5、Filter的執(zhí)行由Servlet容器回調(diào)完成,而攔截器通常通過動態(tài)代理(反射)的方式來執(zhí)行。
6、Filter的生命周期由Servlet容器管理,而攔截器則可以通過IoC容器來管理,因此可以通過注入等方式來獲取其他Bean的實例,因此使用會更方便。
過濾器和攔截器非常相似,但是它們有很大的區(qū)別最簡單明了的區(qū)別就是**過濾器可以修改request,而攔截器不能過濾器需要在servlet容器中實現(xiàn),攔截器可以適用于javaEE,javaSE等各種環(huán)境攔截器可以調(diào)用IOC容器中的各種依賴,而過濾器不能過濾器只能在請求的前后使用,而攔截器可以詳細到每個方法**區(qū)別很多,大家可以去查下
總的來說過濾器就是篩選出你要的東西,比如requeset中你要的那部分攔截器在做安全方面用的比較多,比如 權(quán)限驗證
下面是攔截器的例子:
攔截器定義:
實現(xiàn)HandleInterceptor接口
自定義攔截器類實現(xiàn)HandleInterceptor接口,并使用@Component注解標注為一個組件。
@Component注解 是為了 注入spring其他組件方便, 如果沒有這個注解,自動注入為空
@Autowired
UserService userService;
public class MySelfInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("在業(yè)務(wù)處理器處理請求之前被調(diào)用"); //可以進行權(quán)限校驗,安全控制 MyRequestWrapper requestWrapper = new MyRequestWrapper (request); // 讀取請求內(nèi)容 BufferedReader br = requestWrapper.getReader(); String line = null; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line); } // 將json字符串轉(zhuǎn)換為json對象 JSONObject body = JSONObject.parseObject(sb.toString()); //業(yè)務(wù)處理 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("在業(yè)務(wù)處理器處理請求執(zhí)行完成后,生成視圖之前執(zhí)行"); //可以對返回來的ModelAndView進行處理,這個時候還未渲染視圖 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("在DispatcherServlet完全處理完請求后被調(diào)用"); //請求已經(jīng)完成,頁面已經(jīng)渲染,數(shù)據(jù)已經(jīng)返回。這個時候可以做一些資源清理,或者記錄請求調(diào)用時間,做性能監(jiān)控 } }
繼承HandleInterceptorAdapter類
自定義攔截器類繼承HandleInterceptor接口的實現(xiàn)類HandleInterceptorAdapter來定義,并使用@Component注解標注為一個組件。
可以根據(jù)需要覆蓋一些方法
@Component public class MyInterceptor extends HandlerInterceptorAdapter { public SingleLoginInterceptor() { super(); } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return super.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { super.afterCompletion(request, response, handler, ex); } @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { super.afterConcurrentHandlingStarted(request, response, handler); } }
可以看到HandlerInterceptorAdapter類底層是實現(xiàn)了HandlerInterceptor接口,多了兩個方法,要比實現(xiàn)HandlerInterceptor接口的方式功能強大。
這兩個方法都是HandlerInterceptorAdapter類實現(xiàn)的org.springframework.web.servlet.AsyncHandlerInterceptor接口提供的,而AsyncHandlerInterceptor接口又繼承了HandlerInterceptor接口,所以HandlerInterceptorAdapter底層是實現(xiàn)類HandlerInterceptor接口。
自定義攔截器類實現(xiàn)WebRequestInterceptor接口,并使用@Component注解標注為一個組件。
@Component public class MyInterceptor implements WebRequestInterceptor { @Override public void preHandle(WebRequest webRequest) throws Exception { } @Override public void postHandle(WebRequest webRequest, ModelMap modelMap) throws Exception { } @Override public void afterCompletion(WebRequest webRequest, Exception e) throws Exception { } }
兩個實現(xiàn)接口方式的異同點 相同點 都可以實現(xiàn)controller層的攔截請求 不同點
- 1.WebRequestInterceptor的入?yún)ebRequest是包裝了HttpServletRequest 和HttpServletResponse的,通過WebRequest獲取Request中的信息更簡便。
- 2.WebRequestInterceptor的preHandle是沒有返回值的,說明該方法中的邏輯并不影響后續(xù)的方法執(zhí)行,所以這個接口實現(xiàn)就是為了獲取Request中的信息,或者預(yù)設(shè)一些參數(shù)供后續(xù)流程使用。
- 3.HandlerInterceptor的功能更強大也更基礎(chǔ),可以在preHandle方法中就直接拒絕請求進入controller方法。
實現(xiàn)RequestInterceptor接口
此方式為微服務(wù)Feign調(diào)用的自定義攔截器,實現(xiàn)各個微服務(wù)之間的參數(shù)傳遞。
@Configuration public class CenterinsRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { } }
攔截器的注冊
創(chuàng)建一個自定義類,繼承WebMvcConfigurerAdapter類重寫addInterceptors方法。
@Configuration public class WebCofiguration extends WebMvcConfigurerAdapter { @Bean MyInterceptor getMyInterceptor (){ return new MyInterceptor (); } public void addInterceptors(InterceptorRegistry registry) { // 將自己定義的攔截器注入進來進行攔截操作 //registry.addInterceptor(new MySelfInterceptor ()) // 如果是new 出來的對象 會導致 攔截器中自動裝配為空 registry.addInterceptor(getMyInterceptor ()) .addPathPatterns("/**") .excludePathPatterns("/logout"); //過濾器可以添加多個,這里的addPathPatterns的/**是對所有的請求都做攔截。 //excludePathPatterns代表排除url的攔截路徑,即不攔截 } }
此類在SpringBoot2.0以后已經(jīng)廢除,但仍可使用。
推薦使用以下兩種方式來代替此方式。
1. 創(chuàng)建一個自定義類繼承WebMvcConfigurationSupport類,實現(xiàn)addInterceptors。
@Configuration public class MyInterceptorConfig extends WebMvcConfigurationSupport { @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); super.addInterceptors(registry); } }
此方式會導致默認的靜態(tài)資源被攔截,這就需要我們手動將靜態(tài)資源放開。
除了重寫方法外還需要重寫addResourceHandlers方法來釋放靜態(tài)資源
@Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/static/"); super.addResourceHandlers(registry); }
此方式:一個容器內(nèi)只能有一個WebMvcConfigurationSupport的實現(xiàn)類,也就是說不能有多個繼承類,否則只有一個生效,會造成未知的錯誤,如果想在已有實現(xiàn)類的基礎(chǔ)上(基礎(chǔ)jar包中存在webConfig)還想繼續(xù)添加攔截器,可以選擇繼承WebConfig,但是要super.addInterceptors,避免丟失注冊
原因:在WebMvcAutoConfiguration 中 WebMvcConfigurationSupport 是 @ConditionalOnMissingBean 原來SpringBoot做了這個限制,只有當WebMvcConfigurationSupport類不存在的時候才會生效WebMvc自動化配置
2. 實現(xiàn)WebMvcConfigurer接口
@Configuration public class MyInterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 實現(xiàn)WebMvcConfigurer不會導致靜態(tài)資源被攔截 registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); } }
另外可以寫個配置注解,根據(jù)注解攔截需要的方法
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LogData { }
在controller中需要攔截的方法上加上 @LogData
@LogData public ResponseMessage getUserList(@RequestParam Long id) { return ResponseMessage.ok(); }
可以在攔截器中
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //這個方法將在請求處理之前進行調(diào)用。注意:如果該方法的返回值為false ,將視為當前請求結(jié)束,不僅自身的攔截器會失效,還會導致其他的攔截器也不再執(zhí)行。 log.info("進入到攔截器中:preHandle() 方法"); HandlerMethod handlerMethod = (HandlerMethod) handler; LogData loginVerify = handlerMethod.getMethodAnnotation(LogData .class); if (loginVerify == null) { log.info("不需要對該路徑 進行攔截"); return true; }else { log.info("對該路徑 進行攔截"); log.info("業(yè)務(wù)操作..."); return true; } }
攔截器中request請求被讀取一次后,controller獲取為空
繼承HandleInterceptorAdapter類 和 實現(xiàn)HandleInterceptor接口 實現(xiàn)WebRequestInterceptor接口 自定義類實現(xiàn)RequestInterceptor接口
HttpServletRequest的輸入流只能讀取一次的原因當我們調(diào)用getInputStream()方法獲取輸入流時得到的是一個InputStream對象,而實際類型是ServletInputStream,它繼承與InputStream。
InputStream的read()方法內(nèi)部有一個position,標志當前流被讀取到的位置,每讀取一次,該標志就會移動一次,如果讀到最后,read()返回-1,表示已經(jīng)讀取完了,如果想要重新讀取,則需要調(diào)用reset()方法,position就會移動到上次調(diào)用mark的位置,mark默認是0,所有就能重頭再讀了。調(diào)用reset()方法的前提是已經(jīng)重寫了reset()方法,當然能否reset也是有條件的,它取決于markSupported()方法是否返回true。
InputStream默認不實現(xiàn)reset(),并且markSupported()默認也是返回false
我們可以把流讀取出來后用容器存起來,后面就可以多次利用了。JavaEE提供了一個HttpServletRequestWrapper
類,它是一個http請求包裝器,基于裝飾者模式實現(xiàn)類HttpServletRequest界面。
繼承HttpServletRequestWrapper
,將請求體中的流copy一份,可以重寫getinputStream()和getReader()方法,或自定義方法供外部使用
import dm.jdbc.e.e; import dm.jdbc.util.StreamUtil; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import lombok.extern.slf4j.Slf4j; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.StandardCharsets; /** * 重寫 HttpServletRequestWrapper * */ @Slf4j public class MyRequestWrapper extends HttpServletRequestWrapper { private byte[] body; //用于保存讀取body中數(shù)據(jù) public MyRequestWrapper (HttpServletRequest request) throws IOException { super(request); //讀取請求的數(shù)據(jù)保存到本類當中 //body = StreamUtil.readBytes(request.getReader(), "UTF-8"); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try{ inputStream = request.getInputStream(); if(inputStream != null){ bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead =-1; while((bytesRead = bufferedReader.read(charBuffer)) >0){ stringBuilder.append(charBuffer,0, bytesRead); } }else{ stringBuilder.append(""); } }catch (Exception e){ }finally { if(inputStream != null){ inputStream.close(); } if(bufferedReader != null){ bufferedReader.close(); } } body = stringBuilder.toString().getBytes(); } //覆蓋(重寫)父類的方法 @SuppressFBWarnings("DM_DEFAULT_ENCODING") @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } //覆蓋(重寫)父類的方法 @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { // TODO Auto-generated method stub return false; } @Override public boolean isReady() { // TODO Auto-generated method stub return false; } @Override public void setReadListener(ReadListener arg0) { // TODO Auto-generated method stub } }; } /** * 獲取body中的數(shù)據(jù) * @return */ public byte[] getBody() { return body; } /** * 把處理后的參數(shù)放到body里面 * @param body */ public void setBody(byte[] body) { this.body = body; } }
定義過濾器
import lombok.extern.slf4j.Slf4j; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * 過濾器 * */ @Slf4j @WebFilter(urlPatterns = "/*", filterName = "logSignDataFilter") public class LogSignDataFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { ServletRequest requestWrapper = null; if(request instanceof HttpServletRequest){ requestWrapper = new MyRequestWrapper ((HttpServletRequest) request); } if(requestWrapper == null){ filterChain.doFilter(request, response); }else{ filterChain.doFilter(requestWrapper, response); } } @Override public void init(FilterConfig config) throws ServletException { } }
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot后端數(shù)據(jù)校驗實戰(zhàn)操作指南
在項?開發(fā)中,對于前端提交的表單,后臺接?接收到表單數(shù)據(jù)后,為了保證程序的嚴謹性,通常后端會加?業(yè)務(wù)參數(shù)的合法校驗操作來避免程序的?技術(shù)性?bug,這篇文章主要給大家介紹了關(guān)于SpringBoot后端數(shù)據(jù)校驗的相關(guān)資料,需要的朋友可以參考下2022-07-07Java后端服務(wù)間歇性響應(yīng)慢的問題排查與解決
之前在公司內(nèi)其它團隊找到幫忙排查的一個后端服務(wù)連接超時問題,問題的表現(xiàn)是服務(wù)部署到線上后出現(xiàn)間歇性請求響應(yīng)非常慢(大于10s),但是后端業(yè)務(wù)分析業(yè)務(wù)日志時卻沒有發(fā)現(xiàn)慢請求,所以本文給大家介紹了Java后端服務(wù)間歇性響應(yīng)慢的問題排查與解決,需要的朋友可以參考下2025-03-03Java 實戰(zhàn)項目之誠途旅游系統(tǒng)的實現(xiàn)流程
讀萬卷書不如行萬里路,只學書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SpringBoot+Vue+maven+Mysql實現(xiàn)一個精美的物流管理系統(tǒng),大家可以在過程中查缺補漏,提升水平2021-11-11MyBatis 如何配置多個別名 typeAliasesPackage
這篇文章主要介紹了MyBatis 如何配置多個別名 typeAliasesPackage,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01