SpringBoot之?dāng)r截器與過濾器解讀
SpringBoot 攔截器 過濾器
1、過濾器和攔截器觸發(fā)時(shí)機(jī)不一樣,過濾器是在請(qǐng)求進(jìn)入容器后,但請(qǐng)求進(jìn)入servlet之前進(jìn)行預(yù)處理的。請(qǐng)求結(jié)束返回也是,是在servlet處理完后,返回給前端之前。
2、攔截器可以獲取IOC容器中的各個(gè)bean,而過濾器就不行,因?yàn)閿r截器是spring提供并管理的,spring的功能可以被攔截器使用,在攔截器里注入一個(gè)service,可以調(diào)用業(yè)務(wù)邏輯。而過濾器是JavaEE標(biāo)準(zhǔn),只需依賴servlet api ,不需要依賴spring。
3、過濾器的實(shí)現(xiàn)基于回調(diào)函數(shù)。而攔截器(代理模式)的實(shí)現(xiàn)基于反射
4、Filter是依賴于Servlet容器,屬于Servlet規(guī)范的一部分,而攔截器則是獨(dú)立存在的,可以在任何情況下使用。
5、Filter的執(zhí)行由Servlet容器回調(diào)完成,而攔截器通常通過動(dòng)態(tài)代理(反射)的方式來執(zhí)行。
6、Filter的生命周期由Servlet容器管理,而攔截器則可以通過IoC容器來管理,因此可以通過注入等方式來獲取其他Bean的實(shí)例,因此使用會(huì)更方便。
過濾器和攔截器非常相似,但是它們有很大的區(qū)別最簡(jiǎn)單明了的區(qū)別就是**過濾器可以修改request,而攔截器不能過濾器需要在servlet容器中實(shí)現(xiàn),攔截器可以適用于javaEE,javaSE等各種環(huán)境攔截器可以調(diào)用IOC容器中的各種依賴,而過濾器不能過濾器只能在請(qǐng)求的前后使用,而攔截器可以詳細(xì)到每個(gè)方法**區(qū)別很多,大家可以去查下
總的來說過濾器就是篩選出你要的東西,比如requeset中你要的那部分?jǐn)r截器在做安全方面用的比較多,比如 權(quán)限驗(yàn)證
下面是攔截器的例子:
攔截器定義:
實(shí)現(xiàn)HandleInterceptor接口
自定義攔截器類實(shí)現(xiàn)HandleInterceptor接口,并使用@Component注解標(biāo)注為一個(gè)組件。
@Component注解 是為了 注入spring其他組件方便, 如果沒有這個(gè)注解,自動(dòng)注入為空
@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ù)處理器處理請(qǐng)求之前被調(diào)用"); //可以進(jìn)行權(quán)限校驗(yàn),安全控制 MyRequestWrapper requestWrapper = new MyRequestWrapper (request); // 讀取請(qǐng)求內(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對(duì)象 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ù)處理器處理請(qǐng)求執(zhí)行完成后,生成視圖之前執(zhí)行"); //可以對(duì)返回來的ModelAndView進(jìn)行處理,這個(gè)時(shí)候還未渲染視圖 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("在DispatcherServlet完全處理完請(qǐng)求后被調(diào)用"); //請(qǐng)求已經(jīng)完成,頁(yè)面已經(jīng)渲染,數(shù)據(jù)已經(jīng)返回。這個(gè)時(shí)候可以做一些資源清理,或者記錄請(qǐng)求調(diào)用時(shí)間,做性能監(jiān)控 } }
繼承HandleInterceptorAdapter類
自定義攔截器類繼承HandleInterceptor接口的實(shí)現(xiàn)類HandleInterceptorAdapter來定義,并使用@Component注解標(biāo)注為一個(gè)組件。
可以根據(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類底層是實(shí)現(xiàn)了HandlerInterceptor接口,多了兩個(gè)方法,要比實(shí)現(xiàn)HandlerInterceptor接口的方式功能強(qiáng)大。
這兩個(gè)方法都是HandlerInterceptorAdapter類實(shí)現(xiàn)的org.springframework.web.servlet.AsyncHandlerInterceptor接口提供的,而AsyncHandlerInterceptor接口又繼承了HandlerInterceptor接口,所以HandlerInterceptorAdapter底層是實(shí)現(xiàn)類HandlerInterceptor接口。
自定義攔截器類實(shí)現(xiàn)WebRequestInterceptor接口,并使用@Component注解標(biāo)注為一個(gè)組件。
@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 { } }
兩個(gè)實(shí)現(xiàn)接口方式的異同點(diǎn) 相同點(diǎn) 都可以實(shí)現(xiàn)controller層的攔截請(qǐng)求 不同點(diǎn)
- 1.WebRequestInterceptor的入?yún)ebRequest是包裝了HttpServletRequest 和HttpServletResponse的,通過WebRequest獲取Request中的信息更簡(jiǎn)便。
- 2.WebRequestInterceptor的preHandle是沒有返回值的,說明該方法中的邏輯并不影響后續(xù)的方法執(zhí)行,所以這個(gè)接口實(shí)現(xiàn)就是為了獲取Request中的信息,或者預(yù)設(shè)一些參數(shù)供后續(xù)流程使用。
- 3.HandlerInterceptor的功能更強(qiáng)大也更基礎(chǔ),可以在preHandle方法中就直接拒絕請(qǐng)求進(jìn)入controller方法。
實(shí)現(xiàn)RequestInterceptor接口
此方式為微服務(wù)Feign調(diào)用的自定義攔截器,實(shí)現(xiàn)各個(gè)微服務(wù)之間的參數(shù)傳遞。
@Configuration public class CenterinsRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { } }
攔截器的注冊(cè)
創(chuàng)建一個(gè)自定義類,繼承WebMvcConfigurerAdapter類重寫addInterceptors方法。
@Configuration public class WebCofiguration extends WebMvcConfigurerAdapter { @Bean MyInterceptor getMyInterceptor (){ return new MyInterceptor (); } public void addInterceptors(InterceptorRegistry registry) { // 將自己定義的攔截器注入進(jìn)來進(jìn)行攔截操作 //registry.addInterceptor(new MySelfInterceptor ()) // 如果是new 出來的對(duì)象 會(huì)導(dǎo)致 攔截器中自動(dòng)裝配為空 registry.addInterceptor(getMyInterceptor ()) .addPathPatterns("/**") .excludePathPatterns("/logout"); //過濾器可以添加多個(gè),這里的addPathPatterns的/**是對(duì)所有的請(qǐng)求都做攔截。 //excludePathPatterns代表排除url的攔截路徑,即不攔截 } }
此類在SpringBoot2.0以后已經(jīng)廢除,但仍可使用。
推薦使用以下兩種方式來代替此方式。
1. 創(chuàng)建一個(gè)自定義類繼承WebMvcConfigurationSupport類,實(shí)現(xiàn)addInterceptors。
@Configuration public class MyInterceptorConfig extends WebMvcConfigurationSupport { @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); super.addInterceptors(registry); } }
此方式會(huì)導(dǎo)致默認(rèn)的靜態(tài)資源被攔截,這就需要我們手動(dòng)將靜態(tài)資源放開。
除了重寫方法外還需要重寫addResourceHandlers方法來釋放靜態(tài)資源
@Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/static/"); super.addResourceHandlers(registry); }
此方式:一個(gè)容器內(nèi)只能有一個(gè)WebMvcConfigurationSupport的實(shí)現(xiàn)類,也就是說不能有多個(gè)繼承類,否則只有一個(gè)生效,會(huì)造成未知的錯(cuò)誤,如果想在已有實(shí)現(xiàn)類的基礎(chǔ)上(基礎(chǔ)jar包中存在webConfig)還想繼續(xù)添加攔截器,可以選擇繼承WebConfig,但是要super.addInterceptors,避免丟失注冊(cè)
原因:在WebMvcAutoConfiguration 中 WebMvcConfigurationSupport 是 @ConditionalOnMissingBean 原來SpringBoot做了這個(gè)限制,只有當(dāng)WebMvcConfigurationSupport類不存在的時(shí)候才會(huì)生效WebMvc自動(dòng)化配置
2. 實(shí)現(xiàn)WebMvcConfigurer接口
@Configuration public class MyInterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 實(shí)現(xiàn)WebMvcConfigurer不會(huì)導(dǎo)致靜態(tài)資源被攔截 registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); } }
另外可以寫個(gè)配置注解,根據(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 { //這個(gè)方法將在請(qǐng)求處理之前進(jìn)行調(diào)用。注意:如果該方法的返回值為false ,將視為當(dāng)前請(qǐng)求結(jié)束,不僅自身的攔截器會(huì)失效,還會(huì)導(dǎo)致其他的攔截器也不再執(zhí)行。 log.info("進(jìn)入到攔截器中:preHandle() 方法"); HandlerMethod handlerMethod = (HandlerMethod) handler; LogData loginVerify = handlerMethod.getMethodAnnotation(LogData .class); if (loginVerify == null) { log.info("不需要對(duì)該路徑 進(jìn)行攔截"); return true; }else { log.info("對(duì)該路徑 進(jìn)行攔截"); log.info("業(yè)務(wù)操作..."); return true; } }
攔截器中request請(qǐng)求被讀取一次后,controller獲取為空
繼承HandleInterceptorAdapter類 和 實(shí)現(xiàn)HandleInterceptor接口 實(shí)現(xiàn)WebRequestInterceptor接口 自定義類實(shí)現(xiàn)RequestInterceptor接口
HttpServletRequest的輸入流只能讀取一次的原因當(dāng)我們調(diào)用getInputStream()方法獲取輸入流時(shí)得到的是一個(gè)InputStream對(duì)象,而實(shí)際類型是ServletInputStream,它繼承與InputStream。
InputStream的read()方法內(nèi)部有一個(gè)position,標(biāo)志當(dāng)前流被讀取到的位置,每讀取一次,該標(biāo)志就會(huì)移動(dòng)一次,如果讀到最后,read()返回-1,表示已經(jīng)讀取完了,如果想要重新讀取,則需要調(diào)用reset()方法,position就會(huì)移動(dòng)到上次調(diào)用mark的位置,mark默認(rèn)是0,所有就能重頭再讀了。調(diào)用reset()方法的前提是已經(jīng)重寫了reset()方法,當(dāng)然能否reset也是有條件的,它取決于markSupported()方法是否返回true。
InputStream默認(rèn)不實(shí)現(xiàn)reset(),并且markSupported()默認(rèn)也是返回false
我們可以把流讀取出來后用容器存起來,后面就可以多次利用了。JavaEE提供了一個(gè)HttpServletRequestWrapper
類,它是一個(gè)http請(qǐng)求包裝器,基于裝飾者模式實(shí)現(xiàn)類HttpServletRequest界面。
繼承HttpServletRequestWrapper
,將請(qǐng)求體中的流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); //讀取請(qǐng)求的數(shù)據(jù)保存到本類當(dāng)中 //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é)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot后端數(shù)據(jù)校驗(yàn)實(shí)戰(zhàn)操作指南
在項(xiàng)?開發(fā)中,對(duì)于前端提交的表單,后臺(tái)接?接收到表單數(shù)據(jù)后,為了保證程序的嚴(yán)謹(jǐn)性,通常后端會(huì)加?業(yè)務(wù)參數(shù)的合法校驗(yàn)操作來避免程序的?技術(shù)性?bug,這篇文章主要給大家介紹了關(guān)于SpringBoot后端數(shù)據(jù)校驗(yàn)的相關(guān)資料,需要的朋友可以參考下2022-07-07Java在web頁(yè)面上的編碼解碼處理及中文URL亂碼解決
這篇文章主要介紹了Java在web頁(yè)面上的編碼解碼處理及中文URL亂碼解決,文中所介紹的兩種使用過濾器解決中文鏈接亂碼的方法非常有效,需要的朋友可以參考下2016-02-02java實(shí)現(xiàn)短地址服務(wù)的方法(附代碼)
大多數(shù)情況下URL太長(zhǎng),字符多,不便于發(fā)布復(fù)制和存儲(chǔ),本文就介紹了通過java實(shí)現(xiàn)短地址服務(wù),減少了許多使用太長(zhǎng)URL帶來的不便,需要的朋友可以參考下2015-07-07使用Java實(shí)現(xiàn)一個(gè)解析CURL腳本小工具
文章介紹了如何使用Java實(shí)現(xiàn)一個(gè)解析CURL腳本的工具,該工具可以將CURL腳本中的Header解析為KV Map結(jié)構(gòu),獲取URL路徑、請(qǐng)求類型,解析URL參數(shù)列表和Body請(qǐng)求體,感興趣的小伙伴跟著小編一起來看看吧2025-02-02Java后端服務(wù)間歇性響應(yīng)慢的問題排查與解決
之前在公司內(nèi)其它團(tuán)隊(duì)找到幫忙排查的一個(gè)后端服務(wù)連接超時(shí)問題,問題的表現(xiàn)是服務(wù)部署到線上后出現(xiàn)間歇性請(qǐng)求響應(yīng)非常慢(大于10s),但是后端業(yè)務(wù)分析業(yè)務(wù)日志時(shí)卻沒有發(fā)現(xiàn)慢請(qǐng)求,所以本文給大家介紹了Java后端服務(wù)間歇性響應(yīng)慢的問題排查與解決,需要的朋友可以參考下2025-03-03Java 實(shí)戰(zhàn)項(xiàng)目之誠(chéng)途旅游系統(tǒng)的實(shí)現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SpringBoot+Vue+maven+Mysql實(shí)現(xiàn)一個(gè)精美的物流管理系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-11-11MyBatis 如何配置多個(gè)別名 typeAliasesPackage
這篇文章主要介紹了MyBatis 如何配置多個(gè)別名 typeAliasesPackage,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01