該系列文檔是本人在學習 Spring MVC 的源碼過程當中總結下來的,可能對讀者不太友好,請結合個人源碼註釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀html
Spring 版本:5.2.4.RELEASEjava
該系列其餘文檔請查看:《精盡 Spring MVC 源碼分析 - 文章導讀》git
HandlerMapping 組件,請求的處理器匹配器,負責爲請求找到合適的 HandlerExecutionChain
處理器執行鏈,包含處理器(handler
)和攔截器們(interceptors
)github
handler
處理器是 Object 類型,能夠將其理解成 HandlerMethod 對象(例如咱們使用最多的 @RequestMapping
註解所標註的方法會解析成該對象),包含了方法的全部信息,經過該對象可以執行該方法web
HandlerInterceptor
攔截器對處理請求進行加強處理,可用於在執行方法前、成功執行方法後、處理完成後進行一些邏輯處理spring
因爲 HandlerMapping 組件涉及到的內容比較多,考慮到內容的排版,因此將這部份內容拆分紅了四個模塊,依次進行分析:數組
在上一篇《HandlerMapping 組件(一)之 AbstractHandlerMapping》文檔中分析了 HandlerMapping 組件的 AbstractHandlerMapping 抽象類,在獲取HandlerExecutionChain
處理器執行鏈時,會去尋找匹配的 HandlerInterceptor 攔截器們,並添加到其中。那麼本文將分享 Spring MVC 的攔截器相關內容mvc
org.springframework.web.servlet.HandlerInterceptor
,處理器攔截器接口,代碼以下:app
public interface HandlerInterceptor { /** * 前置處理,在 {@link HandlerAdapter#handle(HttpServletRequest, HttpServletResponse, Object)} 執行以前 */ default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } /** * 後置處理,在 {@link HandlerAdapter#handle(HttpServletRequest, HttpServletResponse, Object)} 執行成功以後 */ default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } /** * 完成處理,在 {@link HandlerAdapter#handle(HttpServletRequest, HttpServletResponse, Object)} 執行以後(不管成功仍是失敗) * 條件:執行 {@link #preHandle(HttpServletRequest, HttpServletResponse, Object)} 成功的攔截器纔會執行該方法 */ default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
org.springframework.web.servlet.HandlerExecutionChain
,處理器執行鏈,也就是經過 HandlerMapping 組件爲請求找到的處理對象,包含處理器(handler
)和攔截器們(interceptors
)cors
public class HandlerExecutionChain { /** * 處理器 */ private final Object handler; /** * 攔截器數組 */ @Nullable private HandlerInterceptor[] interceptors; /** * 攔截器數組。 * * 在實際使用時,會調用 {@link #getInterceptors()} 方法,初始化到 {@link #interceptors} 中 */ @Nullable private List<HandlerInterceptor> interceptorList; /** * 已成功執行 {@link HandlerInterceptor#preHandle(HttpServletRequest, HttpServletResponse, Object)} 的位置 * * 在 {@link #applyPostHandle} 和 {@link #triggerAfterCompletion} 方法中須要用到,用於倒序執行攔截器的方法 */ private int interceptorIndex = -1; public HandlerExecutionChain(Object handler) { this(handler, (HandlerInterceptor[]) null); } public HandlerExecutionChain(Object handler, @Nullable HandlerInterceptor... interceptors) { if (handler instanceof HandlerExecutionChain) { HandlerExecutionChain originalChain = (HandlerExecutionChain) handler; this.handler = originalChain.getHandler(); this.interceptorList = new ArrayList<>(); // 將原始的 HandlerExecutionChain 的 interceptors 複製到 this.interceptorList 中 CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList); // 將入參的 interceptors 合併到 this.interceptorList 中 CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList); } else { this.handler = handler; this.interceptors = interceptors; } } }
handler
:請求對應的處理器對象,能夠先理解爲 HandlerMethod
對象(例如咱們經常使用的 @RequestMapping
註解對應的方法會解析成該對象),也就是咱們的某個 Method 的全部信息,能夠被執行interceptors
:請求匹配的攔截器數組interceptorList
:請求匹配的攔截器集合,至於爲何要該屬性,我還沒看明白😈interceptorIndex
:記錄已成功執行前置處理的攔截器位置,由於已完成處理只會執行前置處理成功的攔截器,且倒序執行addInterceptor(HandlerInterceptor interceptor)
方法,添加攔截器到 interceptorList
集合中,方法以下:
public void addInterceptor(HandlerInterceptor interceptor) { initInterceptorList().add(interceptor); } private List<HandlerInterceptor> initInterceptorList() { // 若是 interceptorList 爲空,則初始化爲 ArrayList if (this.interceptorList == null) { this.interceptorList = new ArrayList<>(); // 若是 interceptors 非空,則添加到 interceptorList 中 if (this.interceptors != null) { // An interceptor array specified through the constructor CollectionUtils.mergeArrayIntoCollection(this.interceptors, this.interceptorList); } } // 置空 interceptors this.interceptors = null; // 返回 interceptorList return this.interceptorList; }
getInterceptors()
方法,得到 interceptors
數組,方法以下:
@Nullable public HandlerInterceptor[] getInterceptors() { // 將 interceptorList 初始化到 interceptors 中 if (this.interceptors == null && this.interceptorList != null) { this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[0]); } // 返回 interceptors 數組 return this.interceptors; }
applyPreHandle(HttpServletRequest request, HttpServletResponse response)
方法,執行請求匹配的攔截器的前置處理,方法以下:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { // <1> 得到攔截器數組 HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { // <2> 遍歷攔截器數組 for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; // <3> 前置處理 if (!interceptor.preHandle(request, response, this.handler)) { // <3.1> 已完成處理 攔截器 triggerAfterCompletion(request, response, null); // 返回 false ,前置處理失敗 return false; } // <3.2> 標記 interceptorIndex 位置 this.interceptorIndex = i; } } // <4> 返回 true ,前置處理成功 return true; }
得到攔截器數組,經過上面的 getInterceptors()
方法,得到 interceptors
數組
遍歷 interceptors
攔截器數組
依次執行攔截器的前置處理
triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
方法,觸發攔截器們的已完成處理,最後返回 false
interceptorIndex
屬性中,爲了已完成處理只會執行前置處理成功的攔截器,且倒序執行返回 true
,攔截器們的前置處理都成功
applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
方法,執行請求匹配的攔截器的後置處理,方法以下:
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { // 得到攔截器數組 HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { // 遍歷攔截器數組 for (int i = interceptors.length - 1; i >= 0; i--) { // 倒序 HandlerInterceptor interceptor = interceptors[i]; // 後置處理 interceptor.postHandle(request, response, this.handler, mv); } } }
triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
方法,執行請求匹配的攔截器的已完成處理,方法以下:
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception { // 得到攔截器數組 HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { // 遍歷攔截器數組 for (int i = this.interceptorIndex; i >= 0; i--) { // 倒序!!! HandlerInterceptor interceptor = interceptors[i]; try { // 已完成處理 攔截器 interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable ex2) { // 注意,若是執行失敗,僅僅會打印錯誤日誌,不會結束循環 logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); } } } }
interceptorIndex
屬性,只會執行前置處理成功的攔截器們,由於該屬性定義了成功執行前置處理的攔截器的位置HandlerMapping 接口體系的結構以下:
能夠看到它的實現類有許多,這裏來看幾個重要的類
org.springframework.web.servlet.handler.MappedInterceptor
,實現 HandlerInterceptor 接口,支持地址匹配的 HandlerInterceptor 實現類
每個 <mvc:interceptor />
標籤,將被解析成一個 MappedInterceptor 類型的 Bean 攔截器對象
public final class MappedInterceptor implements HandlerInterceptor { /** * 匹配的路徑 */ @Nullable private final String[] includePatterns; /** * 不匹配的路徑 */ @Nullable private final String[] excludePatterns; /** * 攔截器對象 */ private final HandlerInterceptor interceptor; /** * 路徑匹配器 */ @Nullable private PathMatcher pathMatcher; public MappedInterceptor(@Nullable String[] includePatterns, HandlerInterceptor interceptor) { this(includePatterns, null, interceptor); } public MappedInterceptor(@Nullable String[] includePatterns, @Nullable String[] excludePatterns, HandlerInterceptor interceptor) { this.includePatterns = includePatterns; this.excludePatterns = excludePatterns; this.interceptor = interceptor; } public MappedInterceptor(@Nullable String[] includePatterns, WebRequestInterceptor interceptor) { this(includePatterns, null, interceptor); } }
includePatterns
:攔截器須要匹配的請求路徑excludePatterns
:攔截器須要排除的請求路徑pathMatcher
:路徑匹配器interceptor
:攔截器對象經過前面三個屬性去判斷請求是否匹配
matches(String lookupPath, PathMatcher pathMatcher)
方法,判斷請求路徑是否匹配,方法以下:
public boolean matches(String lookupPath, PathMatcher pathMatcher) { PathMatcher pathMatcherToUse = (this.pathMatcher != null ? this.pathMatcher : pathMatcher); // <1> 先判斷該路徑是否在不匹配的路徑中 if (!ObjectUtils.isEmpty(this.excludePatterns)) { for (String pattern : this.excludePatterns) { if (pathMatcherToUse.match(pattern, lookupPath)) { return false; } } } // <2> 若是匹配的路徑爲空,則都匹配經過 if (ObjectUtils.isEmpty(this.includePatterns)) { return true; } // <3> 判斷路徑是否在須要匹配的路徑中 for (String pattern : this.includePatterns) { if (pathMatcherToUse.match(pattern, lookupPath)) { return true; } } return false; }
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return this.interceptor.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { this.interceptor.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { this.interceptor.afterCompletion(request, response, handler, ex); }
都是直接調用interceptor
攔截器對應的方法
<mvc:interceptors>
標籤<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**" /> <mvc:exclude-mapping path="/error/**" /> <bean class="com.fullmoon.study.interceptor.JwtInterceptor" /> </mvc:interceptor> </mvc:interceptors>
每個 <mvc:interceptor />
標籤,將被解析成一個 MappedInterceptor
類型的 Bean 攔截器對象
而後 MappedInterceptor
類型的攔截器在 AbstractHandlerMapping 的 initApplicationContext() -> detectMappedInterceptors
會被掃描到
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) { // 掃描已註冊的 MappedInterceptor 的 Bean 們,添加到 mappedInterceptors 中 // MappedInterceptor 會根據請求路徑作匹配,是否進行攔截 mappedInterceptors.addAll(BeanFactoryUtils .beansOfTypeIncludingAncestors(obtainApplicationContext(), MappedInterceptor.class, true, false) .values()); }
也就是說在初始化 HandlerMapping 組件的時候會掃描到咱們自定義的攔截器,並添加到屬性中
<mvc:interceptor />
標籤如何被解析成MappedInterceptor
對象的?
能夠來看到spring-webmvc
工程的 spring.handlers
文件,以下:
http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
指定了 NamespaceHandler 爲 MvcNamespaceHandler
對象,也就是說<mvc />
標籤會被該對象進行解析,以下:
public class MvcNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser()); registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser()); registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser()); registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser()); registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser()); } }
其中<mvc:interceptor />
標籤則會被 InterceptorsBeanDefinitionParser
對象進行解析,以下:
class InterceptorsBeanDefinitionParser implements BeanDefinitionParser { @Override @Nullable public BeanDefinition parse(Element element, ParserContext context) { context.pushContainingComponent( new CompositeComponentDefinition(element.getTagName(), context.extractSource(element))); RuntimeBeanReference pathMatcherRef = null; if (element.hasAttribute("path-matcher")) { pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher")); } List<Element> interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor"); for (Element interceptor : interceptors) { // 將 <mvc:interceptor /> 標籤解析 MappedInterceptor 對象 RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class); mappedInterceptorDef.setSource(context.extractSource(interceptor)); mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); ManagedList<String> includePatterns = null; ManagedList<String> excludePatterns = null; Object interceptorBean; if ("interceptor".equals(interceptor.getLocalName())) { includePatterns = getIncludePatterns(interceptor, "mapping"); excludePatterns = getIncludePatterns(interceptor, "exclude-mapping"); Element beanElem = DomUtils.getChildElementsByTagName(interceptor, "bean", "ref").get(0); interceptorBean = context.getDelegate().parsePropertySubElement(beanElem, null); } else { interceptorBean = context.getDelegate().parsePropertySubElement(interceptor, null); } mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, includePatterns); mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns); mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean); if (pathMatcherRef != null) { mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef); } String beanName = context.getReaderContext().registerWithGeneratedName(mappedInterceptorDef); context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName)); } context.popAndRegisterContainingComponent(); return null; } private ManagedList<String> getIncludePatterns(Element interceptor, String elementName) { List<Element> paths = DomUtils.getChildElementsByTagName(interceptor, elementName); ManagedList<String> patterns = new ManagedList<>(paths.size()); for (Element path : paths) { patterns.add(path.getAttribute("path")); } return patterns; } }
邏輯不復雜,會將 <mvc:interceptor />
標籤解析 BeanDefinition 對象,beanClass 爲 MappedInterceptor
,解析出來的屬性也會添加至其中,也就會給初始化成 MappedInterceptor
類型的 Spring Bean 到 Spring 上下文中
在 SpringBoot 2.0+ 項目中,添加攔截器的方式能夠以下:
@Component public class JwtInterceptor implements HandlerInterceptor { /** * 前置處理 * * @param handler 攔截的目標,處理器 * @return 該請求是否繼續往下執行 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // JWT 校驗 // 驗證經過,返回 true,不然返回false return true; } /** 後置處理 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** 已完成處理 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } } @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { List<String> excludePath = new ArrayList<>(); // 將攔截器添加至 InterceptorRegistry registry.addInterceptor(jwtInterceptor()).addPathPatterns("/**").excludePathPatterns(excludePath); } @Bean public JwtInterceptor jwtInterceptor() { return new JwtInterceptor(); } }
patterns
路徑沒有設置好,可能在請求過程當中發生的錯誤會被攔截器攔截到,能夠在攔截器中根據自定義註解進行攔截處理由於 JwtInterceptor
不是 MappedInterceptor
類型的攔截器,不會被 AbstractHandlerMapping 探測到,既然這樣子,那麼咱們就直接調用 AbstractHandlerMapping 的 setInterceptors(Object... interceptors)
設置進去不就行了
因爲 Spring 5.0 廢棄了 WebMvcConfigurerAdapter,因此須要經過 WebMvcConfigurer 接口來添加咱們的攔截器,那麼在 Spring Boot 2.0+ 中是如何將 WebMvcConfigurer 添加的攔截器設置到 AbstractHandlerMapping 對象中的呢?接下來開始簡單的分析
先來看到 spring-boot-autoconfigure 項目中的 WebMvcAutoConfiguration 自動配置類,其中有一個內部靜態類 EnableWebMvcConfiguration,繼承了 DelegatingWebMvcConfiguration
對象(spring-webmvc 項目中),部分代碼以下:
@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(WebProperties.class) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { // ... 省略相關代碼 }
回到咱們的 spring-webmvc項目,來看到 org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration
這個類,繼承 WebMvcConfigurationSupport 類,部分代碼以下:
@Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { /** WebMvcConfigurer 組合類,內部方法就是遍歷全部的 WebMvcConfigurer 實現類 */ private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { // <1> 注入全部的 WebMvcConfigurer 實現類到 configurers 中 this.configurers.addWebMvcConfigurers(configurers); } } @Override protected void addInterceptors(InterceptorRegistry registry) { // <2> 調用 WebMvcConfigurer 組合類的 addInterceptors(InterceptorRegistry registry) 方法 this.configurers.addInterceptors(registry); } } // org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite.java class WebMvcConfigurerComposite implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // <3> 依次執行 WebMvcConfigurer 實現類的 addInterceptors 方法,將對應的攔截器添加至 registry 中 for ( WebMvcConfigurer delegate : this.delegates) { delegate.addInterceptors(registry); } } }
configurers
中,示例中咱們自定義的 InterceptorConfig 就會被注入到這裏addInterceptors(InterceptorRegistry registry)
方法,看第 3
步addInterceptors(InterceptorRegistry registry)
方法,將對應的攔截器添加至 registry
中。調用示例中咱們自定義的 InterceptorConfig 方法,則將咱們自定義 JwtInterceptor 攔截器添加至 registry
中了再來看到 org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
這個類,部分代碼以下:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware { @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() { RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping(); mapping.setOrder(0); mapping.setInterceptors(getInterceptors()); // ... 省略相關代碼 return mapping; } protected final Object[] getInterceptors() { // 若 interceptors 未初始化,則進行初始化 if (this.interceptors == null) { // 建立 InterceptorRegistry 對象 InterceptorRegistry registry = new InterceptorRegistry(); // 添加攔截器到 interceptors 中 addInterceptors(registry); // 添加內置攔截器到 interceptors 中 registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService())); registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider())); // 初始化到 interceptors 屬性 this.interceptors = registry.getInterceptors(); } // 若 interceptors 已初始化,則直接返回 return this.interceptors.toArray(); } }
邏輯並不複雜,能夠看到 Spring MVC 用到的 RequestMappingHandlerMapping
對象會經過 addInterceptors(InterceptorRegistry registry)
方法,獲取到咱們自定義InterceptorConfig中添加的JwtInterceptor 攔截器,並設置到 RequestMappingHandlerMapping
對象中
本文對 Spring MVC 處理請求的過程當中使用到的 HandlerMapping 組件中的 HandlerInterceptor
攔截器進行了分析,DispatcherServlet 在處理請求的過程當中,會執行 HandlerMapping 組件中與請求匹配的攔截器,進行一些攔截處理。攔截器的在項目中會常用到,應用場景比較多,例如權限校驗、參數預處理等等,上面也提供了相應的使用示例
攔截器有如下三個方法:
preHandle
:前置處理,在執行方法前執行,所有成功執行纔會往下執行方法postHandle
:後置處理,在成功執行方法後執行,倒序afterCompletion
:已完成處理,無論方法是否成功執行都會執行,不過只會執行前置處理成功的攔截器,倒序多個攔截器的執行順序就是自定義 WebMvcConfigurer 實現類添加攔截器時所加入的順序
參考文章:芋道源碼《精盡 Spring MVC 源碼分析》