首頁 > 軟體

一文了解Spring中攔截器的原理與使用

2022-06-27 14:00:08

1.Spring中的攔截器

在web開發中,攔截器是經常用到的功能。它可以幫我們預先設定資料以及統計方法的執行效率等等。

今天就來詳細的談一下spring中的攔截器。spring中攔截器主要分兩種,一個是HandlerInterceptor,一個是MethodInterceptor。

1.1HandlerInterceptor攔截器

HandlerInterceptor是springMVC專案中的攔截器,它攔截的目標是請求的地址,比MethodInterceptor先執行。其工作原理是當請求來時先進性預處理,如下。

這裡我們可以實現一個通過HandlerInterceptor實現列印請求開始和結束的紀錄檔,如下。

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.實現類

攔截器類

@Component
public class EasyLogControllerInterceptor implements HandlerInterceptor {
 
    /**
     * 在controller呼叫之前執行
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println(request.getRequestURI()+"開始執行");
        return true;
    }
 
    /**
     * 在controller呼叫中執行
     */
    public void postHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception {
    }
 
    /**
     * 在controller呼叫後執行
     */
    public void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println(request.getRequestURI()+"執行結束");
    }
 
}

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");
 
    }
 
 
    //此方法用於設定靜態資源路徑
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/my/");
    }
}

3.執行效果

1.1.1HandlerInterceptor講解

實現一個HandlerInterceptor攔截器可以直接實現HandlerInterceptor介面,也可以繼承HandlerInterceptorAdapter類。這兩種方法殊途同歸,其實HandlerInterceptorAdapter也就是宣告了HandlerInterceptor介面中所有方法的預設實現,而我們在繼承他之後只需要重寫必要的方法。

下面就是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 {  
    }  
  
}  

這三個方法都是幹什麼的,有什麼作用,什麼時候呼叫,不同的攔截器之間是怎樣的呼叫順序呢?

先補一張圖:

這還得參考一下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處理請求的整個過程。首先根據請求找到對應的HandlerExecutionChain,它包含了處理請求的handler和所有的HandlerInterceptor攔截器;然後在呼叫hander之前分別呼叫每個HandlerInterceptor攔截器的preHandle方法,若有一個攔截器返回false,則會呼叫triggerAfterCompletion方法,並且立即返回不再往下執行;若所有的攔截器全部返回true並且沒有出現異常,則呼叫handler返回ModelAndView物件;再然後分別呼叫每個攔截器的postHandle方法;最後,即使是之前的步驟丟擲了異常,也會執行triggerAfterCompletion方法。

1.2 MethodInterceptor攔截器

MethodInterceptor是AOP專案中的攔截器,它攔截的目標是方法,即使不是controller中的方法。具體使用方式可以參考SpringBoot中利用AOP和攔截器實現自定義註解

2.二者的區別

上面的兩種攔截器都能起到攔截的效果,但是他們攔截的目標不一樣,實現的機制不同,所以有的時候適用不同的場景。

HandlerInterceptoer攔截的是請求地址,所以針對請求地址做一些驗證、預處理等操作比較合適。當你需要統計請求的響應時間時MethodInterceptor將不太容易做到,因為它可能跨越很多方法或者只涉及到已經定義好的方法中一部分程式碼。MethodInterceptor利用的是AOP的實現機制,在本文中只說明瞭使用方式,關於原理和機制方面介紹的比較少,因為要說清楚這些需要講出AOP的相當一部分內容。在對一些普通的方法上的攔截HandlerInterceptoer就無能為力了,這時候只能利用AOP的MethodInterceptor。

 另外,還有一個跟攔截器類似的東西----Filter。Filter是Servlet規範規定的,不屬於spring框架,也是用於請求的攔截。但是它適合更粗粒度的攔截,在請求前後做一些編解碼處理、紀錄檔記錄等。而攔截器則可以提供更細粒度的,更加靈活的,針對某些請求、某些方法的組合的解決方案。

另外的另外,用過人人網的ROSE框架的人都會非常喜歡它的攔截器功能。因為它實現了全註解的方式,只要在類的名字上加上攔截器的註解即表示這是一個攔截器。而使用這個攔截器的方法或者controller也只需在方法或controller的上面加上這個攔截器的註解。其實這是一個關注點的轉變,spring的切面控制在組態檔中,組態檔關注哪些地方需要攔截。而在ROSE中,則是在需要攔截的地方關注我要被誰攔截。

以上就是一文了解Spring中攔截器的原理與使用的詳細內容,更多關於Spring攔截器的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com