精盡Spring MVC原始碼分析 - HandlerMapping 元件(二)之 HandlerInterceptor 攔截器

月圓吖發表於2020-12-16

該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 Spring MVC 原始碼分析 GitHub 地址 進行閱讀

Spring 版本:5.2.4.RELEASE

該系列其他文件請檢視:《精盡 Spring MVC 原始碼分析 - 文章導讀》

HandlerMapping 元件

HandlerMapping 元件,請求的處理器匹配器,負責為請求找到合適的 HandlerExecutionChain 處理器執行鏈,包含處理器(handler)和攔截器們(interceptors

  • handler 處理器是 Object 型別,可以將其理解成 HandlerMethod 物件(例如我們使用最多的 @RequestMapping 註解所標註的方法會解析成該物件),包含了方法的所有資訊,通過該物件能夠執行該方法

  • HandlerInterceptor 攔截器對處理請求進行增強處理,可用於在執行方法前、成功執行方法後、處理完成後進行一些邏輯處理

由於 HandlerMapping 元件涉及到的內容比較多,考慮到內容的排版,所以將這部分內容拆分成了四個模組,依次進行分析:

HandlerMapping 元件(二)之 HandlerInterceptor 攔截器

在上一篇《HandlerMapping 元件(一)之 AbstractHandlerMapping》文件中分析了 HandlerMapping 元件的 AbstractHandlerMapping 抽象類,在獲取HandlerExecutionChain 處理器執行鏈時,會去尋找匹配的 HandlerInterceptor 攔截器們,並新增到其中。那麼本文將分享 Spring MVC 的攔截器相關內容

HandlerInterceptor

org.springframework.web.servlet.HandlerInterceptor,處理器攔截器介面,程式碼如下:

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 {
	}
}

HandlerExecutionChain

org.springframework.web.servlet.HandlerExecutionChain,處理器執行鏈,也就是通過 HandlerMapping 元件為請求找到的處理物件,包含處理器(handler)和攔截器們(interceptors

構造方法

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

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

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

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;
}
  1. 獲得攔截器陣列,通過上面的 getInterceptors() 方法,獲得 interceptors 陣列

  2. 遍歷 interceptors 攔截器陣列

  3. 依次執行攔截器的前置處理

    1. 如果有某個攔截器的前置處理失敗,則呼叫 triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) 方法,觸發攔截器們的已完成處理,最後返回 false
    2. 每個攔截器成功執行前置處理後,記錄當前攔截器的位置到 interceptorIndex 屬性中,為了已完成處理只會執行前置處理成功的攔截器,且倒序執行
  4. 返回 true,攔截器們的前置處理都成功

applyPostHandle

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

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屬性,只會執行前置處理成功的攔截器們,因為該屬性定義了成功執行前置處理的攔截器的位置
  • 如果前置處理沒有全部執行成功,或者處理請求的過程中出現異常還是會呼叫該方法,也就是執行已完成處理

HandlerInterceptor 的實現類

HandlerMapping 介面體系的結構如下:

精盡Spring MVC原始碼分析 - HandlerMapping 元件(二)之 HandlerInterceptor 攔截器

可以看到它的實現類有許多,這裡來看幾個重要的類

MappedInterceptor

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

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;
}
  1. 先判斷該路徑是否在不匹配的路徑中
  2. 如果匹配的路徑為空,則都匹配通過
  3. 判斷路徑是否在需要匹配的路徑中
攔截方法的實現
@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攔截器對應的方法

其他

使用示例

1. <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 上下文中

2. Java Config

在 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);
		}
	}
}
  1. 注入所有的 WebMvcConfigurer 實現類到 configurers 中,示例中我們自定義的 InterceptorConfig 就會被注入到這裡
  2. 呼叫 WebMvcConfigurer 組合類的 addInterceptors(InterceptorRegistry registry) 方法,看第 3
  3. 依次執行 WebMvcConfigurer 實現類的 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 原始碼分析》

相關文章