攔截器,英文名為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方法時,添加了指定的兩個攔截器,之後還會再添加兩個默認的攔截器,最終的體現就是requestMappingHandlerMapping
的interceptors
變量。
實例化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作用之前