一文了解Spring中攔截器的原理與使用
1.Spring中的攔截器
在web開發(fā)中,攔截器是經(jīng)常用到的功能。它可以幫我們預(yù)先設(shè)置數(shù)據(jù)以及統(tǒng)計方法的執(zhí)行效率等等。
今天就來詳細的談一下spring中的攔截器。spring中攔截器主要分兩種,一個是HandlerInterceptor,一個是MethodInterceptor。
1.1HandlerInterceptor攔截器
HandlerInterceptor是springMVC項目中的攔截器,它攔截的目標是請求的地址,比MethodInterceptor先執(zhí)行。其工作原理是當請求來時先進性預(yù)處理,如下。

這里我們可以實現(xiàn)一個通過HandlerInterceptor實現(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.實現(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.運行效果


1.1.1HandlerInterceptor講解
實現(xiàn)一個HandlerInterceptor攔截器可以直接實現(xiàn)HandlerInterceptor接口,也可以繼承HandlerInterceptorAdapter類。這兩種方法殊途同歸,其實HandlerInterceptorAdapter也就是聲明了HandlerInterceptor接口中所有方法的默認實現(xiàn),而我們在繼承他之后只需要重寫必要的方法。
下面就是HandlerInterceptorAdapter的代碼,可以看到一個方法只是默認返回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)用順序呢?
先補一張圖:

這還得參考一下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);
}
}
}
代碼有點長,但是它封裝了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項目中的攔截器,它攔截的目標是方法,即使不是controller中的方法。具體使用方式可以參考SpringBoot中利用AOP和攔截器實現(xiàn)自定義注解
2.二者的區(qū)別
上面的兩種攔截器都能起到攔截的效果,但是他們攔截的目標不一樣,實現(xiàn)的機制不同,所以有的時候適用不同的場景。
HandlerInterceptoer攔截的是請求地址,所以針對請求地址做一些驗證、預(yù)處理等操作比較合適。當你需要統(tǒng)計請求的響應(yīng)時間時MethodInterceptor將不太容易做到,因為它可能跨越很多方法或者只涉及到已經(jīng)定義好的方法中一部分代碼。MethodInterceptor利用的是AOP的實現(xiàn)機制,在本文中只說明了使用方式,關(guān)于原理和機制方面介紹的比較少,因為要說清楚這些需要講出AOP的相當一部分內(nèi)容。在對一些普通的方法上的攔截HandlerInterceptoer就無能為力了,這時候只能利用AOP的MethodInterceptor。
另外,還有一個跟攔截器類似的東西----Filter。Filter是Servlet規(guī)范規(guī)定的,不屬于spring框架,也是用于請求的攔截。但是它適合更粗粒度的攔截,在請求前后做一些編解碼處理、日志記錄等。而攔截器則可以提供更細粒度的,更加靈活的,針對某些請求、某些方法的組合的解決方案。
另外的另外,用過人人網(wǎng)的ROSE框架的人都會非常喜歡它的攔截器功能。因為它實現(xiàn)了全注解的方式,只要在類的名字上加上攔截器的注解即表示這是一個攔截器。而使用這個攔截器的方法或者controller也只需在方法或controller的上面加上這個攔截器的注解。其實這是一個關(guān)注點的轉(zhuǎn)變,spring的切面控制在配置文件中,配置文件關(guān)注哪些地方需要攔截。而在ROSE中,則是在需要攔截的地方關(guān)注我要被誰攔截。
以上就是一文了解Spring中攔截器的原理與使用的詳細內(nèi)容,更多關(guān)于Spring攔截器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java求100之內(nèi)的素數(shù)(質(zhì)數(shù))簡單示例
這篇文章主要介紹了java求100之內(nèi)的素數(shù)簡單示例,素數(shù)是一個大于1的自然數(shù),如果除了1和它自身外,不能被其他自然數(shù)整除的數(shù);否則稱為合數(shù)2014-04-04
Java中5種方式實現(xiàn)String反轉(zhuǎn)
下面小編就為大家?guī)硪黄狫ava中5種方式實現(xiàn)String反轉(zhuǎn)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。2016-06-06
mybatis(mybatis-plus)映射文件(XML文件)中特殊字符轉(zhuǎn)義的實現(xiàn)
XML 文件在解析時會將五種特殊字符進行轉(zhuǎn)義,本文主要介紹了mybatis(mybatis-plus)映射文件(XML文件)中特殊字符轉(zhuǎn)義的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2023-12-12
springboot日志文件名稱叫l(wèi)ogback-spring.xml的原因解析
這篇文章主要介紹了springboot日志文件名稱為什么叫l(wèi)ogback-spring.xml,本文給大家講解的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08
java 定時器Timer和TimerTask的使用詳解(執(zhí)行和暫停)
這篇文章主要介紹了java 定時器Timer和TimerTask的使用詳解(執(zhí)行和暫停),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-11-11
Mybatis generator修改Mapper.java文件實現(xiàn)詳解
這篇文章主要為大家介紹了Mybatis generator修改Mapper.java文件實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09
在SpringBoot: SpringBoot里面創(chuàng)建導(dǎo)出Excel的接口教程
這篇文章主要介紹了在SpringBoot: SpringBoot里面創(chuàng)建導(dǎo)出Excel的接口教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10

