本文基于以前的文章【深刻浅出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