biancheng-Spring MVC-HandlerMapping
一、HandlerMapping
作用是根据当前请求的找到对应的 Handler,并将 Handler(执行程序)与一堆 HandlerInterceptor(拦截器)封装到 HandlerExecutionChain 对象中。在 HandlerMapping 接口的内部只有一个方法,如下:
- HandlerExecutionChain getHandler(HttpServletRequest request);
HandlerMapping 是由 DispatcherServlet 调用,DispatcherServlet 会从容器中取出所有 HandlerMapping 实例并遍历,让 HandlerMapping 实例根据自己实现类的方式去尝试查找 Handler,而 HandlerMapping 具体有哪些实现类下面就会详细分析。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // 这些 HandlerMapping 在容器初始化时创建,在 initHandlerMappings 时放入集合中 for (HandlerMapping hm : this.handlerMappings) { HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
另外上面说到的 Handler 有可能是一个 HandlerMethod(封装了 Controller 中的方法)对象,也有可能是一个 Controller 对象、 HttpRequestHandler 对象或 Servlet 对象,而这个 Handler 具体是什么对象,也是与所使用的 HandlerMapping 实现类有关。如下图所示,可以看到 HandlerMapping 实现类有两个分支,分别继承自 AbstractHandlerMethodMapping(得到 HandlerMethod)和 AbstractUrlHandlerMapping(得到 HttpRequestHandler、Controller 或 Servlet),它们又统一继承于 AbstractHandlerMapping。
先来看一下 AbstractHandlerMapping,它实现了 HandlerMapping 接口中的 getHandler() 方法,源码如下所示
@Override public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // 根据请求获取执行程序,具体的获取方式由子类决定,getHandlerInternal() 是抽象方法 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 = getApplicationContext().getBean(handlerName); } // 将 Handler 与一堆拦截器包装到 HandlerExecutionChain 对象中 return getHandlerExecutionChain(handler, request); }
可以看到在这个方法中又调用了 getHandlerInternal() 方法获取到了 Handler 对象,而 Handler 对象具体内容是由它的子类去定义的。下面就来一看下 AbstractHandlerMapping 的两个分支子类
1 AbstractUrlHandlerMapping
AbstractUrlHandlerMapping 这个分支获取的 Handler 的类型实际就是一个 Controller 类,所以一个 Controller 只能对应一个请求(或者像 Struts2 那样定位到方法,使同一个业务的方法放在同一个类里),源码如下所示
protected Object getHandlerInternal(HttpServletRequest request) throws Exception { // 根据当前请求获取“查找路径” String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // 根据路径获取 Handler(即Controller),先尝试直接匹配,再尝试模式匹配 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 = getApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } return handler; }
1.1 AbstractUrlHandlerMapping 实现类及使用
1) ControllerClassNameHandlerMapping:根据类名访问 Controller。
<!-- 注册 HandlerMapping --> <bean class="org.springframework.web.servlet.handler.ControllerClassNameHandlerMapping" /> <!-- 注册 Handler --> <bean class="com.controller.TestController" />
2) ControllerBeanNameHandlerMapping:根据 Bean 名访问 Controller,与 BeanNameUrlHandlerMapping 类似,但是bean名称不用遵循URL公约。
<!-- 注册 HandlerMapping --> <bean class="org.springframework.web.servlet.handler.ControllerBeanNameHandlerMapping" /> <!-- 注册 Handler --> <bean id="test" class="com.controller.TestController" />
3) BeanNameUrlHandlerMapping:利用 BeanName 来作为 URL 使用。
<!-- 注册 HandlerMapping --> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" /> <!-- 注册 Handler --> <bean id="/test.do" class="com.controller.TestController" />
4) SimpleUrlHandlerMapping:可以将 URL 与处理器的定义分离,还可以对 URL 进行统一的映射管理。
<!-- 注册 HandlerMapping --> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/test.do">testController</prop> <prop key="/hello.do">testController</prop> </props> </property> </bean> <!-- 注册 Handler --> <bean id="testController" class="com.controller.TestController" />
1.2 Controller 类
使用 AbstractUrlHandlerMapping 的实现类时,需要让控制层的类实现 Controller 接口(一般继承 AbstractController 即可),另外还有一些已经实现了的 Controller 类,如下图所示。但是不论是自己实现 Controller 接口还是使用系统已经实现的类,都只能处理一个请求(除了 MultiActionController 可以通过参数的方式让一个类可以处理多个请求)。
另外下面所有的 Controller 均采用 SimpleUrlHandlerMapping 方式的。
1) UrlFilenameViewController:用于跳转界面,控制器根据请求的URL直接解析出视图名,省去了自己实现 Ccntroller 跳转页面。
<bean id="indexController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
2) ParameterizableViewController:同样用于界面跳转,控制器根据配置的参数来跳转界面,使用方式如下
<bean id="indexController" class="org.springframework.web.servlet.mvc.ParameterizableViewController"> <property name="viewName" value="/index.jsp" /> </bean>
3) ServletForwardingController:将请求转发到 Servlet,使用方式如下
<bean id="indexController" class="org.springframework.web.servlet.mvc.ServletForwardingController"> <property name="servletName" value="indexServlet" /> </bean>
另外还要在 web.xml 中配置要转发到的 Servlet
<servlet> <servlet-name>indexServlet</servlet-name> <servlet-class>com.servlet.ServletForwarding</servlet-class> </servlet>
4) ServletWrappingController:将某个 Servlet 包装为 Controller,所有到 ServletWrappingController 的请求实际上是由它内部所包装的这个 Servlet 实例来处理的,这样可以将这个 Servlet 隐藏起来
5) MultiActionController:一个 Controller 可以写多个方法,分别对应不同的请求,使同一业务的方法可以放在一起了。在使用时让自己的 Controller 类继承 MultiActionController 类,使用方式如下
public class IndexController extends MultiActionController { public ModelAndView add(HttpServletRequest request,HttpServletResponse response) { ModelAndView mv = new ModelAndView(); mv.addObject("message","add"); mv.setViewName("add"); return mv; } public ModelAndView delete(HttpServletRequest request,HttpServletResponse response) { ModelAndView mv = new ModelAndView(); mv.addObject("message","delete"); mv.setViewName("delete"); return mv; } }
配置自己的 Controller 时要配置一个方法名解析器(默认是 InternalPathMethodNameResolver )
<bean id="indexController" class="com.controller.IndexController"> <property name="methodNameResolver"> <!-- InternalPathMethodNameResolver 根据请求路径解析执行方法名 ParameterMethodNameResolver 根据参数解析执行方法名 PropertiesMethodNameResolver 根据 key/value 列表解析执行方法名 --> <bean class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver"> <!-- 指定参数名为action --> <property name="paramName" value="action" /> </bean> </property> </bean>
当我们访问 http://localhost:8080/***/indexAction.do?action=add 时,进入 add() 方法;
当我们访问 http://localhost:8080/***/indexAction.do?action=delete 时,进入 delete() 方法。
2 AbstractHandlerMethodMapping
AbstractHandlerMethodMapping 这个分支获取的 Handler 的类型是 HandlerMethod,即这个 Handler 是一个方法,它保存了方法的信息(如Method),这样一个 Controller 就可以处理多个请求了,源码如下所示
@Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { // 根据当前请求获取“查找路径” String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); // 获取当前请求最佳匹配的处理方法(即Controller类的方法中) HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); }
上述代码中 lookupHandlerMethod() 方法主要工作是在 Map<T, HandlerMethod> handlerMethods 中找到 HandlerMethod,这里的 T 是 HandlerMappingInfo,它封装了 @RequestMapping 注解中的信息。那 HandlerMethod 是怎么创建的(即怎么把 Controller 的方法变成了它),继续看一下源码找到 initHandlerMethods() 方法,这个方法是在这个类创建后调用的,如下所示是它的源码
protected void initHandlerMethods() { // 从容器中获取所有 Bean 的名称,detectHandlerMethodsInAncestorContexts 默认false,不从父容器中查找 //即默认只查找 SpringMVC 的 IOC 容器,不查找它的父容器 Spring 的 IOC 容器 String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : getApplicationContext().getBeanNamesForType(Object.class)); for (String beanName : beanNames) { // 这里的 isHandler()方法由子类实现,判断是否拥有 @Controller 注解或 @RequestMapping 注解 if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) && isHandler(getApplicationContext().getType(beanName))){ // 利用反射得到 Bean 中的 Method 并包装成 HandlerMethod,然后放入 Map 中 detectHandlerMethods(beanName); } } handlerMethodsInitialized(getHandlerMethods()); }
看完上述代码后,可以知道是在 detectHandlerMethods() 方法中将 Bean 的方法转换为 HandlerMethod 对象,具体实现如下
protected void detectHandlerMethods(final Object handler) { // 获取这个 Bean 的 Class 对象 Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()); // 避免重复调用 getMappingForMethod(),getMappingForMethod() 将重新构建 RequestMappingInfo 实例 final Map<Method, T> mappings = new IdentityHashMap<Method, T>(); // 获取被代理前的原始类型 final Class<?> userType = ClassUtils.getUserClass(handlerType); // 获取 Method Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() { @Override public boolean matches(Method method) { // 根据 Method 和它的 @RequestMapping 注解,创建 RequestMappingInfo 对象。 // 这里的 T 就是 RequestMappingInfo,它封装了 @RequestMapping 信息 T mapping = getMappingForMethod(method, userType); if (mapping != null) { mappings.put(method, mapping); return true; } else { return false; } } }); for (Method method : methods) { // 注册 Method 和它的映射,RequestMappingInfo 储存着映射信息 registerHandlerMethod(handler, method, mappings.get(method)); } }
最后在 registerHandlerMethod() 方法中,将 RequestMappingInfo 作为 key,把 Method 包装成 HandlerMethod 作为 value 添加到了 Map<T, HandlerMethod> handlerMethods 中。
protected void registerHandlerMethod(Object handler, Method method, T mapping) { HandlerMethod newHandlerMethod = createHandlerMethod(handler, method); HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping); if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) { throw new IllegalStateException(""); } this.handlerMethods.put(mapping, newHandlerMethod); Set<String> patterns = getMappingPathPatterns(mapping); for (String pattern : patterns) { if (!getPathMatcher().isPattern(pattern)) { this.urlMap.add(pattern, mapping); } } }
1.1 AbstractHandlerMapping 实现类及使用
AbstractHandlerMethodMapping<T> 只有一个实现类 RequestMappingHandlerMapping
HandlerMapping
将 Request
和 Controller
里面的方法对应起来的,为了方便理解,我这里把实现它的子类统称为映射处理器[ps: 自己一时兴起瞎起的,不准确还请见谅]。HandlerMapping
功能就是根据请求匹配到对应的 Handler
,然后将找到的 Handler
和所有匹配的 HandlerInterceptor
(拦截器)绑定到创建的 HandlerExecutionChain
对象上并返回。HandlerMapping
只是一个接口类,不同的实现类有不同的匹对方式,根据功能的不同我们需要在 SpringMVC 容器中注入不同的映射处理器 HandlerMapping
。
1 HandlerMapping
接口
1.1 HandlerMapping
注入
在 DispatcherServlet
类中有下面这个方法
public class DispatcherServlet extends FrameworkServlet {
private void initHandlerMappings(ApplicationContext context) {...}
}
容器被初始化的时候会被调用,加载容器中注入的 HandlerMapping
。其实常用到的 HandlerMapping
都是由 <mvc:annotation-driven /> 标签帮我们注册的(包括 RequestMappingHandlerMapping
和 BeanNameUrlHandlerMapping
),如果没有写该标签系统也会帮我们注入默认的映射器,当然也有些需要我们自己手动注入。
1.2 HandlerExecutionChain
初始化
在 DispatcherServlet
类中,doDispatch(..)
方法总通过调用本类的 getHandler(..)
方法得到 HandlerExecutionChain
对象。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081501576-1360044348.png)
看到这里肯定很模糊,具体 HandlerMapping
内部通过调用 getHandler(..)
得到 HandlerExecutionChain
对象细节请往下看。
1.3 HandlerMapping
接口
在 HandlerMapping
接口中只有一个方法
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081517449-2091661132.png)
首先我们来看下实现类结构
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081529767-1346625683.png)
展开细看
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081539996-212223853.png)
大致上分为两大类 AbstractUrlHandlerMapping
和 AbstractHandlerMethodMapping
。都继承自 AbstractHandlerMapping
抽象类,实现 HandlerMapping
接口。
2 HandlerMapping
接口实现抽象类 AbstractHandlerMapping
在 AbstractHandlerMapping
类中实现 getHandler(..)
接口方法得到 HandlerExecutionChain
对象
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081554583-2046842067.png)
同时 AbstractHandlerMapping
继承 WebApplicationObjectSupport
,初始化时会自动调用模板方法 initApplicationContext
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081608682-1296380938.png)
2.1 AbstractHandlerMapping
实现类分支之一 AbstractUrlHandlerMapping
AbstractUrlHandlerMapping
:URL 映射的抽象基类,提供将处理程序映射到 Controller
,所以该类最终直接返回的 handler
就是 Controller
对象。
实现父抽象类的抽象方法 getHandlerInternal(..)
匹配并返回对应的 Handler
对象。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081622908-1864054326.png)
接下来咱们看看根据路径匹对 handler
的方法 lookupHandler(..)
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081634184-1017799947.png)
上面代码可以看出从 this.handlerMap
中通过 urlPath
匹对找到对应的 handler
对象。那接下来就看下开始是怎么将 handler
对象加入到 this.handlerMap
集合中是关键。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081645699-413746800.png)
那接下来调研这个 protected void registerHandler(String urlPath, Object handler) {}
这个方法什么时候调用,怎么调用就是接下来的重点了。
从源码来看是在 AbstractUrlHandlerMapping
子类里面调用。
AbstractUrlHandlerMapping
的子类从上面截图的类结构可以看出来,大致分为两类:
- 间接继承
AbstractUrlHandlerMapping
的BeanNameUrlHandlerMapping
- 直接继承
AbstractUrlHandlerMapping
的SimpleUrlHandlerMapping
2.1.1 BeanNameUrlHandlerMapping
首先来看其父类 AbstractDetectingUrlHandlerMapping
怎么调用 registerHandler(String urlPath, Object handler)
又怎么匹配到配置在容器中的 handler
并将其注入到 AbstractUrlHandlerMapping
的 this.handlerMap
中。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081656853-655927252.png)
接下来看下 BeanNameUrlHandlerMapping
里面的 determineUrlsForHandler(..)
方法是怎么实现匹对 beanName 是否跟该映射器相关并返回 URLs 的逻辑吧。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081706615-774991873.png)
从上面的源码分析我们可以得知,在 SpringMVC 容器中,且在注入了 BeanNameUrlHandlerMapping
映射器的时候,只要是以 "/" 开头的 bean 的 name,都会作为该映射器匹配的 Handler
对象。
接下来咱们就自定义一个经过该映射器匹对的视图,但是在自定义之前我们需要先了解下 Controller
这个接口。因为使用 AbstractUrlHandlerMapping
的实现类时,需要让控制层的类实现 Controller
接口(一般继承 AbstractController
即可),另外还有一些已经实现了的 Controller
类,如下图所示。但是不论是自己实现 Controller
接口还是继承系统已经实现的类,都只能处理一个请求。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081716968-237281596.png)
首先编写控制层代码
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
System.out.println("访问方法进来了");
ModelAndView mv = new ModelAndView();
mv.setViewName("success");
return mv;
}
}
接下面在 SpringMVC 容器中注入 BeanNameUrlHandlerMapping
映射器和自定义的 Controller
<!-- 注册 HandlerMapping -->
<!--<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>-->
<!-- <mvc:annotation-driven /> 自动帮我们注入 BeanNameUrlHandlerMapping 映射器, 所以与上面手动注入该映射器选其一就行 -->
<mvc:annotation-driven />
<!-- 注册 Handler -->
<bean id="/hello" class="com.ogemray.urlHandlerMapping.HelloController"></bean>
注意手动注入 BeanNameUrlHandlerMapping
映射器记得不要跟 <mvc:annotation-driven /> 标签自动帮我们注入重复(如自己手动注入要么放在 <mvc:annotation-driven />
标签之前,要么直接不写),不然重复注册两次该映射器虽说没有大的影响,但是也有点浪费内存没必要。
注意自定义 Controller
实现类注入 bean
的 id
或 name
必须以 "/" 开头,因为上面分析源码说过,BeanNameUrlHandlerMapping
映射器主要映射以 "/" 开头的 beanName。
2.1.2 SimpleUrlHandlerMapping
在接下来咱们看看该映射器是怎么调用父类的 registerHandler(String urlPath, Object handler)
方法将 handler
加进 AbstractUrlHandlerMapping
的 this.handlerMap
中。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081734346-327334926.png)
从上面源码可以看出 SimpleUrlHandlerMapping
映射器跟前面 BeanNameUrlHandlerMapping
映射器有点不一样。后者是有点类似遍历容器里面有所的 bean
的 name
或 id
找到匹配的,并且 bean
的 name
或 id
有特殊要求,匹配的则加入。而前者则是先将加入该映射器的 handler
先加进该映射器的一个集合属性里面,容器初始化的时候免去了遍历麻烦的步骤。
接下来咱们就自定义一个经过该映射器匹对的视图。
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
System.out.println("访问方法进来了");
ModelAndView mv = new ModelAndView();
mv.setViewName("success");
return mv;
}
}
接下面在 Spring MVC 容器中注入 SimpleUrlHandlerMapping
映射器和自定义的 Controller
<!-- 注册 HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/hello">helloController</prop>
</props>
</property>
</bean>
<!-- 注册 Handler -->
<bean id="helloController" class="com.ogemray.urlHandlerMapping.HelloController"></bean>
2.2 AbstractHandlerMapping
实现类分支之二 AbstractHandlerMethodMapping
AbstractHandlerMethodMapping
最终获取的 handler
是 HandlerMethod
类型对象。实现该抽象类的子类映射器具体映射什么样的方法,怎么实现请求和方法的映射,该抽象类都给出了抽象接口,可高度自定义[ps: 这里给我印象很深,学到了]。
接下来看下该类的实现链:
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081802086-1159008123.png)
具体实现类只有一个 RequestMappingHandlerMapping
,在开始下面正式逻辑分析之前,我们需要了解几个类。
2.2.1 简单了解 HandlerMethod
HandlerMethod
其实可以简单理解为保持方法信息的pojo类
2.2.2 RequestMappingInfo
类
主要用来记录方法上 @RequestMapping()
注解里面的参数,针对 RequestMappingHandlerMapping
映射器来使用。
在容器初始化过程中创建映射器(RequestMappingHandlerMapping
)对象时,会寻找所有被@Controller
注解类中被 @RequestMapping
注解的方法,然后解析方法上的 @RequestMapping
注解,把解析结果封装成 RequestMappingInfo
对象,也就是说RequestMappingInfo
对象是用来装载方法的匹配相关信息,每个匹配的方法都会对应一个 RequestMappingInfo
对象。现在大家应该能明白 RequestMappingInfo
的作用了吧。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081813995-1166363115.png)
PatternsRequestCondition
:模式请求路径过滤器,对应记录和判断@RequestMapping
注解上的value
属性。RequestMethodsRequestCondition
:请求方法过滤器,对应记录和判断@RequestMapping
注解上的method
属性。ParamsRequestCondition
:请求参数过滤器,对应记录和判断@RequestMapping
注解上的params
属性。HeadersRequestCondition
:头字段过滤器,对应记录和判断@RequestMapping
注解上的headers
属性。ConsumesRequestCondition
:请求媒体类型过滤器,对应记录和判断@RequestMapping
注解上的consumes
属性。ProducesRequestCondition
:应答媒体类型过滤器,对应记录和判断@RequestMapping
注解上的produces
属性。RequestConditionHolder
:预留自定义扩展过滤器。
2.2.3 进入 AbstractHandlerMethodMapping
映射器内部
① 首先来看下该类实现父抽象类(AbstractHandlerMapping
) 的抽象方法 getHandlerInternal(..)
匹配并返回对应的 handler
对象。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081831849-1471356697.png)
跟前面的另一个实现分支 AbstractUrlHandlerMapping
实现看起来差不多,都是根据请求路径来匹对,但是内部配对方式有什么不同还需要我们接着往下看。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081908596-1220786821.png)
注意:
Match
就是该抽象类里面自定义的一个内部类,用来记录方法标记信息对象mapping
和方法源信息对象HandlerMethod
。- 当请求为 restful 风格时,将会遍历所有的 mapping,然后一个个匹对,非常耗时和费资源。优化请参考 springMVC在restful风格的性能优化
- 上面的两个抽象方法(
getMatchingMapping(..)
和getMappingComparator(..)
)
前者要实现检查提供的请求映射信息中的条件是否与请求匹配。
后者要实现当一个Request
对应多个mapping
时的择优方案。
② 看下存储映射关系对象(MappingRegistry
)内部结构
说到这里,可能大家对于这个 this.mappingRegistery
对象十分好奇,里面到底是怎么存储数据的,先是可以根据 lookupPath
找到 List<mapping>
,接着后来又根据 mapping
找到 HandlerMethod
对象。
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081927699-1027142535.png)
该实体类里面最重要的两个记录集合分别是 mappingLookup
和 urlLookup
。
-
urlLookup
:主要用来记录lookupPath
请求路径对应的mapping
集合。
这里 Spring 留了一个很活的机制,拿@RequestMapping
注解来说,他的value
属性本身就是一个字符数组,在多重设置中难免有路径重复的,所以最终有可能会出现一个lookupPath
对应多个RequestMappingInfo
,最终在请求过来的时候给了自定义抽象方法让实现类自己实现择优的方式。
MutivalueMap
是 SpringMVC 自定义的一个Map
类,key 对应的 value 是一个集合,这从名字上也能看出来。 -
mappingLookup
:key 是mapping
对象,value 是HandlerMethod
对象,最终是通过lookupPath
在urlLookup
集合中找到对应的mapping
对象,通过mapping
在mappingLookup
集合中找到HandlerMethod
对象。
③ 看下是怎么将映射关系装进缓存(MappingRegistry
) 对象中的
容器初始化的时候都干了些什么
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081947189-721256095.png)
isHandler(..)
是该抽象类定义的抽象方法,由实现类自己去实现匹对哪些类。看下 RequestMappingHandlerMapping
映射器是怎么实现的吧![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308081958735-1396134969.png)
看来 RequestMappingHandlerMapping
映射器,只要类上有 Controller
或 RequestMapping
注解,就符合该映射器管辖范围。
接着解析往下看
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308082007384-201094504.png)
来个分支看下 RequestMappingHandlerMapping
是怎么实现抽象方法 getMappingForMethod(..)
方法的,该映射器都匹配什么样的方法呢?
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308082016841-2084299983.png)
猜也能猜到,RequestMappingHandlerMapping
映射器肯定匹配有 @RequestMapping
注解的方法,并返回该方法的映射信息对象 RequestMappingInfo
对象。
下面就到了最后一步,具体这个映射关系是怎么装入映射器的 MappingRegistry
对象属性的缓存的呢?
![](https://img2022.cnblogs.com/blog/2084611/202203/2084611-20220308082026440-2079166394.png)
3 总结
到这里,关于 SpringMVC 内部是怎么通过 HandlerMapping
映射器将各自对应映射的资源在容器初始的时候装到自身的缓存,在请求过来时又是怎么找到对应的资源返回最终对应的 handler
对象已经描述完了。
现在开发我们基本都不用 AbstractUrlHandlerMapping
这种类型的映射器了,但是 SpringMVC 内部还有用到的地方,例如直接 <mvc:view-controller path="" view-name=""/>
标签配置资源不经过视图控制器直接跳转就用到了 SimpleUrlHandlerMapping
这种映射器。AbstractUrlHandlerMapping
匹对解析对应请求最终返回的 handler
是 Controller
对象。
现在我们习惯直接用 @Controller
和 @RequestMapping
这样注解来描述视图控制器的逻辑,这种资源映射用的是 AbstractHandlerMethodMapping
抽象类的子类 RequestMappingHandlerMapping
映射器,匹对解析对应的请求返回HandlerMethod
对象。
通过研究这种映射,对于我个人来说学到了很多,优秀的设计模式遵循开闭原则,扩展放开修改关闭,高度模块化同时也支持高度自定义话,优秀!!!