一文了解Spring中攔截器的原理與使用
1.Spring中的攔截器
在web開發(fā)中,攔截器是經(jīng)常用到的功能。它可以幫我們預(yù)先設(shè)置數(shù)據(jù)以及統(tǒng)計(jì)方法的執(zhí)行效率等等。
今天就來詳細(xì)的談一下spring中的攔截器。spring中攔截器主要分兩種,一個是HandlerInterceptor,一個是MethodInterceptor。
1.1HandlerInterceptor攔截器
HandlerInterceptor是springMVC項(xiàng)目中的攔截器,它攔截的目標(biāo)是請求的地址,比MethodInterceptor先執(zhí)行。其工作原理是當(dāng)請求來時先進(jìn)性預(yù)處理,如下。
這里我們可以實(shí)現(xiàn)一個通過HandlerInterceptor實(shí)現(xiàn)打印請求開始和結(jié)束的日志,如下。
1.依賴引入
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2.實(shí)現(xiàn)類
攔截器類
@Component public class EasyLogControllerInterceptor implements HandlerInterceptor { /** * 在controller調(diào)用之前執(zhí)行 */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(request.getRequestURI()+"開始執(zhí)行"); return true; } /** * 在controller調(diào)用中執(zhí)行 */ public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * 在controller調(diào)用后執(zhí)行 */ public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println(request.getRequestURI()+"執(zhí)行結(jié)束"); } }
controller類
@RestController public class TestController { @GetMapping("/hello") public Map<String,String> hello(){ Map<String,String> response=new HashMap<>(); response.put("msg","hello"); return response; } }
配置類
@Configuration public class IntercepterConfig implements WebMvcConfigurer { @Autowired private EasyLogControllerInterceptor easyLogControllerInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { //addPathPatterns用于添加攔截路徑 //excludePathPatterns用于添加不攔截的路徑 registry.addInterceptor(easyLogControllerInterceptor).addPathPatterns("/hello"); } //此方法用于配置靜態(tài)資源路徑 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/my/"); } }
3.運(yùn)行效果
1.1.1HandlerInterceptor講解
實(shí)現(xiàn)一個HandlerInterceptor攔截器可以直接實(shí)現(xiàn)HandlerInterceptor接口,也可以繼承HandlerInterceptorAdapter類。這兩種方法殊途同歸,其實(shí)HandlerInterceptorAdapter也就是聲明了HandlerInterceptor接口中所有方法的默認(rèn)實(shí)現(xiàn),而我們在繼承他之后只需要重寫必要的方法。
下面就是HandlerInterceptorAdapter的代碼,可以看到一個方法只是默認(rèn)返回true,另外兩個是空方法:
public abstract class HandlerInterceptorAdapter implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } public void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
這三個方法都是干什么的,有什么作用,什么時候調(diào)用,不同的攔截器之間是怎樣的調(diào)用順序呢?
先補(bǔ)一張圖:
這還得參考一下DispatcherServlet的doDispatch方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; int interceptorIndex = -1; try { ModelAndView mv; boolean errorView = false; try { processedRequest = checkMultipart(request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // Apply preHandle methods of registered interceptors. HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); if (interceptors != null) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); return; } interceptorIndex = i; } } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // Do we need view name translation? if (mv != null && !mv.hasView()) { mv.setViewName(getDefaultViewName(request)); } // Apply postHandle methods of registered interceptors. if (interceptors != null) { for (int i = interceptors.length - 1; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv); } } } catch (ModelAndViewDefiningException ex) { logger.debug("ModelAndViewDefiningException encountered", ex); mv = ex.getModelAndView(); } catch (Exception ex) { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(processedRequest, response, handler, ex); errorView = (mv != null); } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { render(mv, processedRequest, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isDebugEnabled()) { logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request handling"); } } // Trigger after-completion for successful outcome. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); } catch (Exception ex) { // Trigger after-completion for thrown exception. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); throw ex; } catch (Error err) { ServletException ex = new NestedServletException("Handler processing failed", err); // Trigger after-completion for thrown exception. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); throw ex; } finally { // Clean up any resources used by a multipart request. if (processedRequest != request) { cleanupMultipart(processedRequest); } } }
代碼有點(diǎn)長,但是它封裝了springMVC處理請求的整個過程。首先根據(jù)請求找到對應(yīng)的HandlerExecutionChain,它包含了處理請求的handler和所有的HandlerInterceptor攔截器;然后在調(diào)用hander之前分別調(diào)用每個HandlerInterceptor攔截器的preHandle方法,若有一個攔截器返回false,則會調(diào)用triggerAfterCompletion方法,并且立即返回不再往下執(zhí)行;若所有的攔截器全部返回true并且沒有出現(xiàn)異常,則調(diào)用handler返回ModelAndView對象;再然后分別調(diào)用每個攔截器的postHandle方法;最后,即使是之前的步驟拋出了異常,也會執(zhí)行triggerAfterCompletion方法。
1.2 MethodInterceptor攔截器
MethodInterceptor是AOP項(xiàng)目中的攔截器,它攔截的目標(biāo)是方法,即使不是controller中的方法。具體使用方式可以參考SpringBoot中利用AOP和攔截器實(shí)現(xiàn)自定義注解
2.二者的區(qū)別
上面的兩種攔截器都能起到攔截的效果,但是他們攔截的目標(biāo)不一樣,實(shí)現(xiàn)的機(jī)制不同,所以有的時候適用不同的場景。
HandlerInterceptoer攔截的是請求地址,所以針對請求地址做一些驗(yàn)證、預(yù)處理等操作比較合適。當(dāng)你需要統(tǒng)計(jì)請求的響應(yīng)時間時MethodInterceptor將不太容易做到,因?yàn)樗赡芸缭胶芏喾椒ɑ蛘咧簧婕暗揭呀?jīng)定義好的方法中一部分代碼。MethodInterceptor利用的是AOP的實(shí)現(xiàn)機(jī)制,在本文中只說明了使用方式,關(guān)于原理和機(jī)制方面介紹的比較少,因?yàn)橐f清楚這些需要講出AOP的相當(dāng)一部分內(nèi)容。在對一些普通的方法上的攔截HandlerInterceptoer就無能為力了,這時候只能利用AOP的MethodInterceptor。
另外,還有一個跟攔截器類似的東西----Filter。Filter是Servlet規(guī)范規(guī)定的,不屬于spring框架,也是用于請求的攔截。但是它適合更粗粒度的攔截,在請求前后做一些編解碼處理、日志記錄等。而攔截器則可以提供更細(xì)粒度的,更加靈活的,針對某些請求、某些方法的組合的解決方案。
另外的另外,用過人人網(wǎng)的ROSE框架的人都會非常喜歡它的攔截器功能。因?yàn)樗鼘?shí)現(xiàn)了全注解的方式,只要在類的名字上加上攔截器的注解即表示這是一個攔截器。而使用這個攔截器的方法或者controller也只需在方法或controller的上面加上這個攔截器的注解。其實(shí)這是一個關(guān)注點(diǎn)的轉(zhuǎn)變,spring的切面控制在配置文件中,配置文件關(guān)注哪些地方需要攔截。而在ROSE中,則是在需要攔截的地方關(guān)注我要被誰攔截。
以上就是一文了解Spring中攔截器的原理與使用的詳細(xì)內(nèi)容,更多關(guān)于Spring攔截器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringMVC實(shí)現(xiàn)文件的上傳和下載實(shí)例代碼
本篇文章主要介紹了SpringMVC實(shí)現(xiàn)文件的上傳和下載實(shí)例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05Java servlet 使用 PrintWriter 時的編碼與亂碼的示例代碼
本篇文章主要介紹了Java servlet 使用 PrintWriter 時的編碼與亂碼的示例代碼,探討了 PrintWriter 的缺省編碼與普通字符流的缺省編碼的差異,具有一定的參考價值,有興趣的可以了解一下2017-11-11詳解Java中synchronized關(guān)鍵字的死鎖和內(nèi)存占用問題
Java的synchronized關(guān)鍵字用來進(jìn)行線程同步操作,然而這在使用中經(jīng)常會遇到一些問題,這里我們就來詳解Java中synchronized關(guān)鍵字的死鎖和內(nèi)存占用問題:2016-06-06基于IDEA 的遠(yuǎn)程調(diào)試 Weblogic的操作過程
這篇文章主要介紹了基于IDEA 的遠(yuǎn)程調(diào)試 Weblogic的操作過程,本文通過圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09SpringCloud Ribbon與OpenFeign詳解如何實(shí)現(xiàn)服務(wù)調(diào)用
這篇文章主要介紹了SpringCloud Ribbon與OpenFeign實(shí)現(xiàn)服務(wù)調(diào)用的過程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09