本文基於以前的文章【深刻淺出spring】Spring MVC 流程解析 繼續從源碼角度分析 spring MVC 的原理。Spring MVC 的運行流程在上面的文章中已經介紹了,這裏詳細介紹 url --> handler 的映射過程,即 hanndler mappinghtml
回顧下上文介紹的流程1:git
首先用戶發送請求,DispatcherServlet
實現了Servlet
接口,整個請求處理流:HttpServlet.service -> FrameworkServlet.doGet -> FrameworkServlet.processRequest -> DispatcherServlet.doService -> DispatcherServlet.doDispatch
web
相關源碼以下:spring
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerExecutionChain mappedHandler = null; ...... mappedHandler = getHandler(processedRequest); ...... } @Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } } return null; } 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); } HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request); CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
拆解doDispatch
中相關步驟,和 handler mapping 相關的流程可慨括爲DispatcherServlet.doDispatch --> DispatcherServlet.getHandler --> AbstractHandlerMapping.getHandler --> xxx.getHandlerInternal
,這裏的xxx
是AbstractHandlerMapping
的子類或實現類,核心方法getHandlerInternal
是抽象方法,具體實現由其子類實現。segmentfault
spring mvc中默認的HandlerMapping有4種(即此處 handlerMappings 列表中的成員):mvc
下面依次分析每一個HandlerMapping的原理。附錄有具體的類關係圖app
由附錄中的類關係圖可知,此類繼承自AbstractHandlerMethodMapping
,如上節分析 DispatcherServlet
中的 getHandler
方法最終回調用子類的 getHandlerInternal
方法,RequestMappingHandlerMapping
沒有重寫getHandlerInternal
,故這裏直接調用AbstractHandlerMethodMapping.getHandlerInternal
,源碼以下:cors
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); if (logger.isDebugEnabled()) { logger.debug("Looking up handler method for path " + lookupPath); } this.mappingRegistry.acquireReadLock(); try { HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); if (logger.isDebugEnabled()) { if (handlerMethod != null) { logger.debug("Returning handler method [" + handlerMethod + "]"); } else { logger.debug("Did not find handler method for [" + lookupPath + "]"); } } return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } } protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } ...... }
上述方法是根據請求url獲取對應處理方法,而後封裝成 HandlerExecutionChain, 即HandlerExecutionChain 中的 handler 是 HandlerMethod 類型。 這裏不得不提一下 MappingRegistry,是 RequestMappingHandlerMapping 中重要的類,lookupHandlerMethod 的核心邏輯就是從 MappingRegistry 中獲取 url 對應的 HandlerMethod。ide
class MappingRegistry { private final Map<T, MappingRegistration<T>> registry = new HashMap<>(); private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>(); private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>(); private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>(); private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>(); private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); }
對於 RequestMappingHandlerMapping,這裏的 T = RequestMappingInfo。聯結 urlLookup 和 mappingLookup 便可實現從 url --> HandlerMethod 的映射,實現獲取對應請求的處理方法。
不難發現,這裏的 url --> HandlerMethod 的尋找過程本質就是 Map 的 key-value,那麼這些 map,即 MappingRegistry 的實例是何時初始化的呢?spring-boot
從 RequestMappingHandlerMapping
關係圖中能夠發現RequestMappingHandlerMapping
實現的接口InitializingBean
,InitializingBean
的做用見Spring Bean 初始化之InitializingBean, init-method 和 PostConstruct,我的感受仍是講的很清楚了。跟蹤源代碼能夠發現,MappingRegistry
的初始化入口是AbstractHandlerMethodMapping.initHandlerMethods
,源碼以下:
protected void initHandlerMethods() { if (logger.isDebugEnabled()) { logger.debug("Looking for request mappings in application context: " + getApplicationContext()); } String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); for (String beanName : beanNames) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { Class<?> beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isDebugEnabled()) { logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex); } } if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } } } handlerMethodsInitialized(getHandlerMethods()); }
能夠看到其中過濾 bean 使用了RequestMappingHandlerMapping.isHandler
,對註解了@Controller, @RequestMapping
的 bean 纔會進行後續操做。即核心方法detectHandlerMethods
,經過反射的方式,將@RequestMapping
中的url和對應的處理方法註冊到mappingRegistry
中,從而實現了上面所屬的映射查找。
@Override protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); } protected void detectHandlerMethods(final Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { final Class<?> userType = ClassUtils.getUserClass(handlerType); Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } }); if (logger.isDebugEnabled()) { logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods); } for (Map.Entry<Method, T> entry : methods.entrySet()) { Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType); T mapping = entry.getValue(); // 註冊到mappingRegistry中 registerHandlerMethod(handler, invocableMethod, mapping); } } }
RequestMappingHandlerMapping
沒有重寫 getHandlerInternal
,直接使用父類 AbstractHandlerMethodMapping
的 getHandlerInternal
RequestMappingHandlerMapping
會將 @Controller, @RequestMapping
註解的類方法註冊爲 handler,每一個請求url能夠對應一個 handler method 來處理RequestMappingHandlerMapping
的映射關係由 MappingRegistry
維護,經過多個map聯結,便可找到請求對應的處理器RequestMappingHandlerMapping
的映射關係初始化入口是 afterProperties
方法,由於其實現了接口 InitializingBean
此類的getHandlerInternal
邏輯由其父類AbstractUrlHandlerMapping
實現
protected Object getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); Object 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 ("/".equals(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); } } if (handler != null && logger.isDebugEnabled()) { logger.debug("Mapping [" + lookupPath + "] to " + handler); } else if (handler == null && logger.isTraceEnabled()) { logger.trace("No handler mapping found for [" + lookupPath + "]"); } return handler; }
其中核心邏輯是Object lookupHandler(String urlPath, HttpServletRequest request)
,這個方法的尋找邏輯根據優先級簡單歸納爲以下幾步:
Map<String, Object> handlerMap
中根據請求 url 獲取對應的處理方法AntPathMatcher
從handlerMap
中獲取匹配最好的url,並獲取對應的處理方法一樣,這裏只是一個獲取查找的過場,核心點是handlerMap
的初始化邏輯。
從類圖關係中能夠發現,SimpleUrlHandlerMapping
實現了ApplicationContextAware
接口,從ApplicationContextAwareProcessor.invokeAwareInterfaces
能夠發現,此接口的setApplicationContext
會在bean初始化的時候被調用
private void invokeAwareInterfaces(Object bean) { if (bean instanceof Aware) { if (bean instanceof EnvironmentAware) { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) { ((MessageSourceAware) bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } } } // SimpleUrlHandlerMapping的具體實現 public void initApplicationContext() throws BeansException { super.initApplicationContext(); registerHandlers(this.urlMap); } protected void registerHandlers(Map<String, Object> urlMap) throws BeansException { if (urlMap.isEmpty()) { logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping"); } else { urlMap.forEach((url, handler) -> { // Prepend with slash if not already present. if (!url.startsWith("/")) { url = "/" + url; } // Remove whitespace from handler bean name. if (handler instanceof String) { handler = ((String) handler).trim(); } registerHandler(url, handler); }); } }
ApplicationContextAware.setApplicationContext
註冊 url - handler 的映射,這裏的 handler 對應了一個處理類,調用方法handleRequest
處理,而不像RequestMappingHandlerMapping
,是類中的一個方法。SimpleUrlHandlerMapping
處理的 url 好比以/
開頭SimpleUrlHandlerMapping
的 url - handler 映射來源來自成員urlMap
,故須要經過配置注入,例子以下:<bean id="helloController" class="com.raistudies.ui.controller.HelloController"/> <bean id="urlHandler" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/hello.htm" value-ref="helloController"/> <entry key="/sayHello*" value-ref="helloController"/> <entry key="/welcome.html" value-ref="helloController"/> <entry key="/welcomeUser*" value-ref="helloController"/> </map> </property> </bean>
SimpleUrlHandlerMapping
的初始化再WebMvcAutoConfiguration
中完成:@Bean public SimpleUrlHandlerMapping faviconHandlerMapping() { SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler())); return mapping; }
首先來看下getHandlerInternal
方法
public Object getHandlerInternal(HttpServletRequest request) throws Exception { for (MediaType mediaType : getAcceptedMediaTypes(request)) { if (mediaType.includes(MediaType.TEXT_HTML)) { return super.getHandlerInternal(request); } } return null; }
能夠看到,邏輯比較簡單,請求的 media type 需爲 text/html, 而後執行父類的方法AbstractUrlHandlerMapping.getHandlerInternal
protected Object getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); Object 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 ("/".equals(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); } } if (handler != null && logger.isDebugEnabled()) { logger.debug("Mapping [" + lookupPath + "] to " + handler); } else if (handler == null && logger.isTraceEnabled()) { logger.trace("No handler mapping found for [" + lookupPath + "]"); } return handler; }
從源碼中能夠看到,先經過lookupHandler
尋找處理方法,若找不到且請求爲/
,則返回rootHandler
。
再看下WebMvcAutoConfiguration
對WelcomePageHandlerMapping
的初始化源碼,這裏的getWelcomPage
會在以下路徑下尋找:
@Bean public WelcomePageHandlerMapping welcomePageHandlerMapping( ApplicationContext applicationContext) { return new WelcomePageHandlerMapping( new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(), this.mvcProperties.getStaticPathPattern()); }
WelcomePageHandlerMapping
默認經過 WebMvcAutoConfiguration
初始化,將處理器直接註冊爲rootHandler
,默認處理請求/
,默認返回規定路徑下的index.html
一樣的先看getHandlerInternal
方法,BeanNameUrlHandlerMapping
和其父類AbstractDetectingUrlHandlerMapping
都沒有重寫,故getHandlerInternal
執行的是父類AbstractUrlHandlerMapping
的方法,邏輯同SimpleUrlHandlerMapping
。
不一樣點是handlerMap
的初始化。經過源碼閱讀,發現AbstractDetectingUrlHandlerMapping
重寫了initApplicationContext
,具體邏輯以下:
@Override public void initApplicationContext() throws ApplicationContextException { super.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"); } } } } // BeanNameUrlHandlerMapping @Override protected String[] determineUrlsForHandler(String beanName) { List<String> urls = new ArrayList<>(); if (beanName.startsWith("/")) { urls.add(beanName); } String[] aliases = obtainApplicationContext().getAliases(beanName); for (String alias : aliases) { if (alias.startsWith("/")) { urls.add(alias); } } return StringUtils.toStringArray(urls); }
根據源碼不難發現,對於名稱以/
開頭的bean,會被註冊到handlerMap
中,key即爲bean name,value即爲對應的bean,處理請求的時候會調用bean的handleRequest
方法.
配置示例:
bean name="/hello.htm" class="com.raistudies.ui.comtroller.HelloController"/> <bean name="/sayHello*" class="com.raistudies.ui.comtroller.HelloController"/> <bean id="urlHandler" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
RequestMappingHandlerMapping
, BeanNameUrlHandlerMapping
, SimpleUrlHandlerMapping
, WelcomePageHandlerMapping
RequestMappingHandlerMapping
的映射緯度是請求 --> 方法
,另外3個都是請求 --> 類
。RequestMappingHandlerMapping
的應用場景豐富,處理動態請求的最佳實踐,另外3個基本上用來處理靜態資源的BeanNameUrlHandlerMapping
, SimpleUrlHandlerMapping
原理很接近,最大的區別是BeanNameUrlHandlerMapping
的bean名稱就是url,必須以/
開頭,所以相比SimpleUrlHandlerMapping
少了一步 url --> handler實例
的配置Ordered.HIGHEST_PRECEDENCE -- Ordered.LOWEST_PRECEDENCE
WelcomePageHandlerMapping
示例請求:http://localhost:8080/
,會渲染 static 目錄下的 index.htmlBeanNameUrlHandlerMapping
示例請求:http://localhost:8080/beanNameUrl.html
,對應的處理器BeanNameController
SimpleUrlHandlerMapping
示例請求:http://localhost:8080/simpleUrl.html
,對應的處理器SimpleUrlController
,配置ControllerConfig
RequestMappingHandlerMapping
示例請求:http://localhost:8080/data/model
,對應的處理方法DemoController.getModel