SpringBoot MVC(9)攔截器與MVC總結

語言: CN / TW / HK

攔截器,英文名為Interceptor,在項目中可以用來做登錄和權限的攔截,下面將從實際應用出發,來探索它的實現。

代碼準備

攔截器bean

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        // 這裏的handler一般為HandlerMethod類,可以通過它得知當前運行的類和方法相關信息,以此來處理一些業務的邏輯
        System.out.println("login");
        return true;
    }
}
@Component
public class PermissionInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        System.out.println("permission");
        return true;
    }
}
複製代碼

配置類

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Resource
    private LoginInterceptor loginInterceptor;
    @Resource
    private PermissionInterceptor permissionInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(permissionInterceptor).addPathPatterns("/hello/test/**");
        registry.addInterceptor(loginInterceptor).addPathPatterns("/hello/demo/**");

    }
}
複製代碼

啟動階段

beanName為org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration對應的類為WebMvcAutoConfiguration.EnableWebMvcConfiguration,它繼承了父類DelegatingWebMvcConfiguration,如下圖,在依賴注入階段,會添加IoC容器中的WebMvcConfigurer類到configurers變量,並最終在delegates變量體現。這就是配置類必須要實現WebMvcConfigurer接口的原因了

在實例化beanName為requestMappingHandlerMapping的時候,其對應類為RequestMappingHandlerMapping,會走入以下方法中。

WebMvcAutoConfiguration->EnableWebMvcConfiguration->requestMappingHandlerMapping():
    // 執行的是父類的requestMappingHandlerMapping()
    return super.requestMappingHandlerMapping();
WebMvcConfigurationSupport->requestMappingHandlerMapping():
    RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
    mapping.setOrder(0);
    // 設置攔截器
    mapping.setInterceptors(getInterceptors());
    mapping.setContentNegotiationManager(mvcContentNegotiationManager());
    // Cors,如果配置類有重寫addCorsMappings方法,將會添加
    mapping.setCorsConfigurations(getCorsConfigurations());
WebMvcConfigurationSupport->getInterceptors():
    InterceptorRegistry registry = new InterceptorRegistry();
    // 添加攔截器
    addInterceptors(registry);
    // 再添加兩個默認的攔截器
    registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
    registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
    this.interceptors = registry.getInterceptors();
DelegatingWebMvcConfiguration->addInterceptors():
    this.configurers.addInterceptors(registry);
WebMvcConfigurerComposite->addInterceptors():
    // 這裏的delegates一個是默認的攔截器,一個是配置的攔截器
    for (WebMvcConfigurer delegate : this.delegates) {
        delegate.addInterceptors(registry);
    }
複製代碼

這裏的delegates一個是默認的配置類WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter,它的addInterceptors方法為空。另一個是配置的InterceptorConfig,執行到其下的addInterceptors方法時,添加了指定的兩個攔截器,之後還會再添加兩個默認的攔截器,最終的體現就是requestMappingHandlerMappinginterceptors變量。

實例化RequestMappingHandlerMapping之後,在初始化階段,還會做映射的查找與註冊

運行階段

處理方法與攔截器的指定

AbstractHandlerMapping->getHandler():
    Object handler = getHandlerInternal(request);
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
AbstractHandlerMapping->getHandlerExecutionChain():
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
                for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
            if (interceptor instanceof MappedInterceptor) {
                MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
                // 當url符合配置的攔截器incluePatterns時,會添加
                // 符合excludePatterns時,不會添加
                if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                    chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }
        // 默認的兩個是直接添加的
        else {
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
複製代碼

Object handler = getHandlerInternal(request)根據不同的請求返回不同的處理方法(過程參考映射的匹配Servlet初始化中的常用handlerMapping介紹),之後在chain添加符合的攔截器鏈,返回此對象。

攔截器的調用

DispatcherServlet->doDispatch():
    // 調用各攔截器的preHandle方法,如果為false,調用afterCompletion方法之後返回
    // 這邊判斷為false直接return了
    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
    }
    ......
    // 調用各攔截器的postHandle方法
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    ......
    // 最後,無論成功或是異常捕獲,都會調用攔截器的afterCompletion方法
複製代碼

總結

HandlerInterceptor的三個方法在不同階段被調用,最核心的還是它的preHandle方法,如果返回false,可以直接不執行接口方法,我想,這就是稱其為“攔截器”的原因了吧。

SpringMVC流程圖

從圖中可以看出,過濾器(Filter)與攔截器(Interceptor)最大的區別是過濾器在Servlet作用之前