Spring MVC組件之HandlerMapping
HandlerMapping概述
HandlerMapping組件的作用解析一個個Request請求,并找到相應處理這個Request的Handler。Handler一般可以理解為Controller控制器里的一個方法。
HandlerMapping組件主要做了兩件事件。
在組件初始化時,會把Request請求和對應的Handler進行注冊,其實就是把Request和對應的Handler以鍵值對的形式存在一個map中。
解析一個個Request請求,在注冊的map中找相應的handler。
在SpringMvc的源碼中,HandlerMapping定義為一個接口。接口除了定義幾個屬性字段,只定義了一個getHandler方法。

HandlerMapping類圖

從以上類圖中可以看出,HandlerMapping組件主要是分了兩個系列。一個系列主要繼承至AbstractHandlerMethodMapping。另一個系列主要繼承至AbstractUrlHandlerMapping。而AbstractHandlerMethodMapping和AbstractUrlHandlerMapping這兩個抽象類又都是繼承至AbstractHandlerMapping。
AbstractHandlerMapping
AbstractHandlerMapping是一個抽象類,它實現(xiàn)了HandlerMapping接口。AbstractHandlerMapping是一個非?;A的類,HandlerMapping的所有子類系列都是繼承自它。AbstractHandlerMapping采用了模板模式進行了整體的設計,各個子類通過覆寫模板方法來實現(xiàn)相應功能。
AbstractHandlerMappin抽象類既然繼承了HandlerMapping接口,它肯定事要實現(xiàn)getHandler方法。在AbstractHandlerMappin類中,具體代碼如下:
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// Ensure presence of cached lookupPath for interceptors and others
if (!ServletRequestPathUtils.*hasCachedPath*(request)) {
initLookupPath(request);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !DispatcherType.*ASYNC*.equals(request.getDispatcherType())) {
logger.debug("Mapped to " + executionChain.getHandler());
}
if (hasCorsConfigurationSource(handler) || CorsUtils.*isPreFlightRequest*(request)) {
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {
config.validateAllowCredentials();
}
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
getHandler方法中要特別注意getHandlerInternal(request)方法,該方法是一個模板方法。在AbstractHandlerMapping類中,getHandlerInternal(request)方法只是一個抽象的方法,沒有做任何的事情。該方法專門預留給AbstractHandlerMapping的子類來覆寫,從而實現(xiàn)自己的業(yè)務邏輯。
AbstractHandlerMapping還繼承自WebApplicationObjectSupport類,并重寫了該父類的initApplicationContext方法。
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
}
在initApplicationContext方法,定義了三個方法。
- extendInterceptors(this.interceptors)
extendInterceptors是一個模板方法,給子類提供了一個修改this.interceptors攔截器的入口。
- detectMappedInterceptors(this.adaptedInterceptors)
detectMappedInterceptors方法是將Spring MVC中所有MappedInterceptor類型的bean,添加到this.adaptedInterceptors的集合中。
3.initInterceptors()
initInterceptors是初始化攔截器,將所有的this.interceptors集合中的攔截器包裝后,添加到this.adaptedInterceptors的集合中。
AbstractHandlerMethodMapping系列
AbstractHandlerMethodMapping
AbstractHandlerMethodMapping是一個非常重要的類,AbstractHandlerMethodMapping除了繼承AbstractHandlerMapping抽象類,它還實現(xiàn)了InitializingBean接口。
Handler的注冊
在Spring中如果一個類實現(xiàn)了InitializingBean接口,Spring容器就會在實例化該Bean時,會調用Bean的afterPropertiesSet方法。
AbstractHandlerMethodMapping類中,在覆寫InitializingBean接口的afterPropertiesSet方法時,完成了初始化注冊的工作。這也是HandlerMapping組件的第一步工作,把Request請求和對應的Handler先進行注冊。
AbstractHandlerMethodMapping類的afterPropertiesSet方法具體代碼如下圖所示。
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
可以看出在AbstractHandlerMethodMapping類的afterPropertiesSet方法中,調用了initHandlerMethods()方法??磇nitHandlerMethods()方法的名稱,就知道它其實是完成了初始化的工作。
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(***SCOPED_TARGET_NAME_PREFIX***)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
在initHandlerMethods()方法中,通過getCandidateBeanNames方法,先獲取Spring容器中所有的Bean的名稱。過濾掉名稱以” scopedTarget.”打頭的Bean。通過循環(huán)再把Bean的名稱傳入processCandidateBean(beanName)方法。
processCandidateBean(beanName)方法主要做了三件事情。
通過Bean的名稱找到Bean對應的類型。
通過isHandler方法,過濾掉不符合條件的Bean。isHandler方法是一個模板方法,具體邏輯是在子類RequestMappingHandlerMapping中實現(xiàn)的。isHandler方法只會選擇含有@Controller和@RequestMapping注解的bean。
通過detectHandlerMethods方法,建立request請求和handler之間的對應映射關系。
detectHandlerMethods方法
detectHandlerMethods方法主要做了兩件事情。
1. 使用getMappingForMethod方法,通過handler找到有@RequestMapping注解的方法。在AbstractHandlerMethodMapping類中,getMappingForMethod方法在只是一個抽象方法,具體實現(xiàn)是在子類RequestMappingHandlerMapping類中,實現(xiàn)具體的業(yè)務邏輯。
2. 使用registerHandlerMethod方法,將找到的方法進行注冊。所謂注冊其實就是將找到的HandlerMothed放到一個Map中。如果同一個HandlerMothed進行第二次注冊,就會拋出異常。
Handler的查找
在AbstractHandlerMethodMapping類中,覆寫了父類AbstractHandlerMapping的模板方法getHandlerInternal方法。
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
在getHandlerInternal方法中,主要做了三件事件。
在initLookupPath方法中,把一個request轉換成一個url.
再通過request和lookupPath 這兩個參數(shù),使用lookupHandlerMethod方法,找到相應的HandlerMethod。
在找到HandlerMethod對象的情況下,會調用HandlerMethod的createWithResolvedBean方法。該方法會判斷這個HandlerMethod對象。是不是String 類型的。如果是String 類型則說明他只是一個Bean的名稱,會根據這個Bean的名稱在Spring容器中找到該Bean。根據這個Bean,會創(chuàng)建一個新的HandlerMethod對像返回。
RequestMappingInfoHandlerMapping
RequestMappingInfoHandlerMapping類主要是重寫了父類的getMatchingMapping方法。getMatchingMapping方法會根據當前的請求,返回一個匹配了各種RequestCondition的RequestMappingInfo對象。
SpringMVC會根據這個RequestMappingInfo對象來獲取對應的Handler。
RequestMappingHandlerMapping
Spring MVC容器在初始化HandlerMapping類型的組件時,最終初始化的AbstractHandlerMethodMapping系列組件,就是RequestMappingHandlerMapping。
RequestMappingHandlerMapping主要是重寫了父類的三個方法。
- afterPropertiesSet方法
重寫了父類的初始化方法,在afterPropertiesSet方法中,創(chuàng)建了一個BuilderConfiguration類型的對象。然后對BuilderConfiguration對象,進行了屬性的設置。
- isHandler方法
主要是用于判斷獲取何種類型的Handler。對Handler起到一個過濾的作用,只取@Controller和@RequestMapping兩種注解類型的Handler。
- getMappingForMethod方法
getMappingForMethod方法主要是通過method來創(chuàng)建相應的RequestMappingInfo對象。程序先從method對象上獲取RequestMapping注解。再從RequestMapping注解的信息,來創(chuàng)建“路徑匹配”, ”頭部匹配”, ”請求參數(shù)匹配”, ”可產生MIME匹配”, ”可消費MIME匹配”, ”請求方法匹配”,等RequestCondition接口的實例。最后組合這些RequestCondition接口實例,來創(chuàng)建一個RequestMappingInfo對象。SpringMVC會根據RequestMappingInfo對象來注冊請求和Handler的映射關系。
RequestMappingInfo對象實現(xiàn)了RequestCondition接口。接口RequestCondition是Spring MVC對一個請求匹配條件的概念建模。
AbstractUrlHandlerMapping系列
AbstractUrlHandlerMapping系列從名字就可以看出,主要是處理url和handler的關系。AbstractUrlHandlerMapping類先將url和handler的映射關系存在一個Map。再通過url來獲取相應的handler。
AbstractUrlHandlerMapping
AbstractUrlHandlerMapping類跟AbstractHandlerMethodMapping類一樣,也繼承了AbstractHandlerMapping這個抽象類。所有url跟HandlerMapping相關系列的子類,都是繼承至AbstractUrlHandlerMapping這個父類。
AbstractUrlHandlerMapping同時也實現(xiàn)了MatchableHandlerMapping的接口。MatchableHandlerMapping接口定義了一個用于匹配的match方法。
HandlerMap的注冊
在AbstractUrlHandlerMapping類中,registerHandler這個方法是專門負責對handler進行注冊的。Handler的注冊,其實就是把url和對應的handler存儲到handlerMap這個哈希map中。
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
resolvedHandler = applicationContext.getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isTraceEnabled()) {
logger.trace("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isTraceEnabled()) {
logger.trace("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (getPatternParser() != null) {
this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
}
if (logger.isTraceEnabled()) {
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
在registerHandler方法中,主要做了4件事情。
如果handler不是懶加載的,且handler是字符串類型的。此時就把handler作為一個BeanName,在Spring 容器中獲取這個handler的bean對象。
對urlPath進行了校驗,如果一個urlPath對應了多個不同的handler,代碼就會拋出異常。
對特殊的urlPath進行了單獨的處理,對”/”,”/*”分別調用了setRootHandler方法和setDefaultHandler方法進行了特殊的處理。
在handlerMap中記錄url和handler的對應關系。通過this.handlerMap.put(urlPath, resolvedHandler)這句代碼,把url和handler通過鍵值對的方式存儲到hash散列中。
Handler的查找
在AbstractUrlHandlerMapping類中,具體實現(xiàn)了如何從一個url來獲取相應的的handler。AbstractUrlHandlerMapping類中,重寫了getHandlerInternal方法。通過url來獲取handler的邏輯,就寫在getHandlerInternal方法中。
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);
Object handler;
if (usesPathPatterns()) {
RequestPath path = ServletRequestPathUtils.*getParsedRequestPath*(request);
handler = lookupHandler(path, lookupPath, request);
}
else {
handler = lookupHandler(lookupPath, request);
}
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if (StringUtils.*matchesCharacter*(lookupPath, '/')) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
return handler;
}
在getHandlerInternal方法中,程序會先根據request獲取到一個url。再通過lookupHandler方法來獲取相應的Handler。
lookupHandler方法
lookupHandler從方法名稱就可以知道,就是通過url來查找對應的Handler。lookupHandler首先會從handlerMap中直接獲取。若找到了Handler,則會直接返回。
若不能直接從handlerMap中獲取,則會使用PathPattern進行模式匹配。如果一個url匹配了多個PathPattern,會對多個PathPattern進行排序,取最優(yōu)的一個。
獲取到了PathPattern后,通過PathPattern從pathPatternHandlerMap獲取Handler。如果Handler為String類型,那么這個Handler是Handle Bean的名稱。再根據這個BeanName在Spring容器中找到相應的Bean。
獲取到Handler后,會使用validateHandler對這個Handler進行校驗。validateHandler是一個模板方法,主要留給子類進行擴展實現(xiàn)。
最后會使用buildPathExposingHandler方法,為這個Handler增加一些攔截器。
buildPathExposingHandler
buildPathExposingHandler方法主要是給Handler注冊了兩個內部的攔截器。他們分別是PathExposingHandlerInterceptor和UriTemplateVariablesHandlerInterceptor攔截器。
AbstractDetectingUrlHandlerMapping
在AbstractDetectingUrlHandlerMapping類中,主要是重寫了父類的initApplicationContext()方法。在initApplicationContext()方法中,調用了detectHandlers()方法。
protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
if (logger.isDebugEnabled()) {
logger.debug("Looking for URL mappings in application context: " + applicationContext);
}
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.*beanNamesForTypeIncludingAncestors*(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
for (String beanName : beanNames) {
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.*isEmpty*(urls)) {
// URL paths found: Let's consider it a handler.
registerHandler(urls, beanName);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
}
}
}
}
在調用了detectHandlers()方法中,主要做了以下幾個步驟。
獲取Spring 容器中所有bean的名稱。
循環(huán)遍歷所有bean的名稱,對每一個beanName調用determineUrlsForHandler方法,獲取這個beanName對應的url。
再調用父類的registerHandler(urls, beanName)方法,對url和handler進行注冊。
determineUrlsForHandler(beanName)方法在AbstractDetectingUrlHandlerMapping類中,只是一個虛方法,專門留給子類來具體實現(xiàn)。
BeanNameUrlHandlerMapping
Spring MVC容器在初始化HandlerMapping類型的組件時,默認初始化AbstractUrlHandlerMapping系列的組件時,初始化的就是BeanNameUrlHandlerMapping組件。
BeanNameUrlHandlerMapping類繼承至AbstractDetectingUrlHandlerMapping這個父類,子類中主要是重寫了determineUrlsForHandler方法。determineUrlsForHandler方法中,主要是篩選了Bean的名稱或者Bean的別名以“/”斜杠開頭的Bean。最后會把Bean的名稱和對應的Bean對象注冊到handlerMap這個HashMap對象中。
SimpleUrlHandlerMapping
SimpleUrlHandlerMapping類繼承至AbstractUrlHandlerMapping這個父類。SimpleUrlHandlerMapping類中有一個Map<String, Object>類型的urlMap屬性。我們可以把url和對應的HandlerMapping的放到這個屬性中。SimpleUrlHandlerMapping類會在registerHandlers(Map<String, Object> urlMap)方法中,遍歷這個urlMap,再調用AbstractUrlHandlerMapping父類的registerHandler(String urlPath, Object handler)方法,對url和HandlerMapping進行注冊。
除了Map<String, Object>類型的urlMap屬性。SimpleUrlHandlerMapping類中也提供了使用Properties來進行url的注冊。通過setMappings(Properties mappings)方法,SimpleUrlHandlerMapping會把mappings合并到urlMap屬性中。再走urlMap屬性的注冊邏輯。