前言
本文基于之前的文章【深入浅出spring】Spring MVC 流程解析 继续从源码角度分析 spring MVC 的原理。Spring MVC 的运行流程在上面的文章中已经介绍了,这里详细介绍 url --> handler 的映射过程,即 hanndler mapping
总流程
回顾下上文介绍的流程1:
首先用户发送请求,DispatcherServlet
实现了Servlet
接口,整个请求处理流:HttpServlet.service -> FrameworkServlet.doGet -> FrameworkServlet.processRequest -> DispatcherServlet.doService -> DispatcherServlet.doDispatch
相关源码如下:
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
是抽象方法,具体实现由其子类实现。
spring mvc中默认的HandlerMapping有4种(即此处 handlerMappings 列表中的成员):
- RequestMappingHandlerMapping
- BeanNameUrlHandlerMapping
- SimpleUrlHandlerMapping
- WelcomePageHandlerMapping
下面依次分析每个HandlerMapping的原理。附录有具体的类关系图
RequestMappingHandlerMapping
处理器获取
由附录中的类关系图可知,此类继承自AbstractHandlerMethodMapping
,如上节分析 DispatcherServlet
中的 getHandler
方法最终回调用子类的 getHandlerInternal
方法,RequestMappingHandlerMapping
没有重写getHandlerInternal
,故这里直接调用AbstractHandlerMethodMapping.getHandlerInternal
,源码如下:
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。
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 的实例是什么时候初始化的呢?
映射关系写入
从 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
SimpleUrlHandlerMapping
处理器获取
此类的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;
}
WelcomePageHandlerMapping
获取处理器
首先来看下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
会在如下路径下寻找:
- classpath:/META-INF/resources/index.html
- classpath:/resources/index.html
- classpath:/static/index.html
- classpath:/public/index.html
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(
ApplicationContext applicationContext) {
return new WelcomePageHandlerMapping(
new TemplateAvailabilityProviders(applicationContext),
applicationContext, getWelcomePage(),
this.mvcProperties.getStaticPathPattern());
}
总结
-
WelcomePageHandlerMapping
默认通过WebMvcAutoConfiguration
初始化,将处理器直接注册为rootHandler
,默认处理请求/
,默认返回规定路径下的index.html
BeanNameUrlHandlerMapping
获取处理器
同样的先看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"/>
总结
- 默认的handlerMapping有4类:
RequestMappingHandlerMapping
,BeanNameUrlHandlerMapping
,SimpleUrlHandlerMapping
,WelcomePageHandlerMapping
-
RequestMappingHandlerMapping
的映射纬度是请求 --> 方法
,另外3个都是请求 --> 类
。RequestMappingHandlerMapping
的应用场景丰富,处理动态请求的最佳实践,另外3个基本上用来处理静态资源的 -
BeanNameUrlHandlerMapping
,SimpleUrlHandlerMapping
原理很接近,最大的区别是BeanNameUrlHandlerMapping
的bean名称就是url,必须以/
开头,因此相比SimpleUrlHandlerMapping
少了一步url --> handler实例
的配置 - 当容器中有多个handlerMapping实例时,会根据实例的优先级从高到底依次遍历,一旦找到对应 handler 后就会结束。优先级通过mapping的order属性设置,值越小,优先级越高。若不显示设置,默认就是max,即优先级最低。范围
Ordered.HIGHEST_PRECEDENCE -- Ordered.LOWEST_PRECEDENCE
示例
- 代码下载地址:handler_mapping_sample
-
WelcomePageHandlerMapping
示例请求:http://localhost:8080/
,会渲染 static 目录下的 index.html -
BeanNameUrlHandlerMapping
示例请求:http://localhost:8080/beanNameUrl.html
,对应的处理器BeanNameController
-
SimpleUrlHandlerMapping
示例请求:http://localhost:8080/simpleUrl.html
,对应的处理器SimpleUrlController
,配置ControllerConfig
-
RequestMappingHandlerMapping
示例请求:http://localhost:8080/data/model
,对应的处理方法DemoController.getModel
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。