SpringMVC的流程分析(二)—— HandlerMapping组件
1.HandlerMapping的类结构
如上图所示,HandlerMapping接口有一个叫做;getHandler()的方法,这个方法是用来回去HandlerMapping对应的处理器的,由此也就可以看出HandlerMapping主要是用来映射请求和处理器的。
AbstractHandlerMapping实现了HandlerMapping接口,还继承了WebApplicationObjectSupport,而WebApplicationObjectSupport最终实现了ApplicationContextAware接口,这个接口使用来扩展
Spring的,ApplicationContextAware可以对应用上下文进行加工,加入自己的逻辑,SpringMVC就是通过它来实现的url和处理器的映射。
接下来我们看到AbstractHandlerMethodMapping和AbstractUrlHandlerMapping都继承了AbstractHandlerMapping,这两个类分别从不同的角度来映射请求和处理器。
2.AbstractHandlerMapping具体的实现
2.1 AbstractUrlHandlerMapping
我们分开来看,首先分析AbstractUrlHandlerMapping,这个类是用来映射url和handler的,它维护了一个handlerMap用来存储url相应的处理器,它的实现类AbstractDetectingUrlHandlerMapping重写了
initApplictionContext()方法:
@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
detectHandlers();
}
可以看到他调用了父类的initApplicationContext方法,然后又调用了detectHandlers()方法来处理请求映射,看detectHandler()源码:
protected void detectHandlers() throws BeansException { String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().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");
}
}
}
}
第一步,获取所有的beanname,第二步,根据这个beanName查询它是不是一个处理器,并取出他的url,
determineUrlsForHandler有多种实现,我们拿AbstractControllerUrlHandlerMapping举例说明:
@Override
protected String[] determineUrlsForHandler(String beanName) {
Class<?> beanClass = getApplicationContext().getType(beanName);
if (isEligibleForMapping(beanName, beanClass)) {
return buildUrlsForHandler(beanName, beanClass);
}
else {
return null;
}
}
首先从应用上下文中根据beanname获取到bean的class对象,然后判断有没有成为处理器的资格,即判断是否实现了Controller接口。
然后调用buildUrlsForHandler()方法获取处理器的url,然后返回。
接下来执行 registerHandler(urls, beanName) :
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
registerHandler(url,beanname)
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { Object resolvedHandler = handler; // Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
resolvedHandler = getApplicationContext().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.isInfoEnabled()) {
logger.info("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isInfoEnabled()) {
logger.info("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
首先从上下文拿到一个处理器实例,然后根据url的不同,将它设置到不同的处理器存储位置,例如,如果url是"/"就将处理器设置为根处理器,
如果url是"/*"就将处理器设置为默认处理器。如果都不是就放进handleMap中。
通过以上步骤,我们就将上下文中所有的bean循环了一遍,只要是符合HandlerMapping的规则的处理器,就会将他的映射关系存储起来。
2.2 AbstractHandlerMethodMapping
AbstractHandlerMethodMapping和AbstractUrlMethodMapping有所不同,他实现了InitializingBean接口,通过实现afterPropertiesSet()方法来扩展相应的业务
public void afterPropertiesSet() {
initHandlerMethods();
}
调用了initHandlerMethods()
protected void initHandlerMethods() {
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class)); for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
首先获取所有的beanname,然后遍历这些bean,判断他是不是处理器,不同的实现有不同的判断方法,那我们最常用的@Controller为例说明
protected boolean isHandler(Class<?> beanType) {
return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}
判断这个类有没有@Controller或者@ReuqestMapping注解就可以了.
接下来执行detectHandlerMethods(beanName)
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType =
(handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()); // Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
final Class<?> userType = ClassUtils.getUserClass(handlerType); Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
@Override
public boolean matches(Method method) {
T mapping = getMappingForMethod(method, userType);
if (mapping != null) {
mappings.put(method, mapping);
return true;
}
else {
return false;
}
}
}); for (Method method : methods) {
registerHandlerMethod(handler, method, mappings.get(method));
}
}
可以看到它首先会得到所有的方法,然后对方法进行过滤,调用matches方法,还是拿@Controller举例:
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = null;
RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (methodAnnotation != null) {
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
info = createRequestMappingInfo(methodAnnotation, methodCondition);
RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
if (typeAnnotation != null) {
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
}
}
return info;
}
可以看到他是判断方法有没有RequestMapping注解来过滤方法的,所有过滤出来的方法都会生成一个 RequestMappingInfo,(它包含该方法的所有的注解信息),放进maps里面,
接下来就是将所有的方法进行注册:
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("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
"' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
} this.handlerMethods.put(mapping, newHandlerMethod);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
} Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
this.urlMap.add(pattern, mapping);
}
} if (this.namingStrategy != null) {
String name = this.namingStrategy.getName(newHandlerMethod, mapping);
updateNameMap(name, newHandlerMethod);
}
}
首先创建一个HandlerMethod对象,用来包装处理器和对应的方法。然后判断是否有重复的handlermethod,然后将映射类和处理类放进handlerMethods中,
可以看到,他还会将url和映射信息放进一个urlMap来记录,这说明多个请求可能对应同一个映射。
以上就是HandlerMapping的大体作用和流程信息,用来呈放web应用的映射信息,关于如何根据request寻找到对应的映射器,将在下一章介绍。
SpringMVC的流程分析(二)—— HandlerMapping组件的更多相关文章
- 框架-springmvc源码分析(二)
框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...
- SpringMVC的流程分析(一)—— 整体流程概括
SpringMVC的整体概括 之前也写过springmvc的流程分析,只是当时理解的还不透彻所以那篇文章就放弃了,现在比之前好了些,想着写下来分享下,也能增强记忆,也希望可以帮助到人,如果文章中有什么 ...
- Android 7.1 WindowManagerService 屏幕旋转流程分析 (二)
一.概述 从上篇[Android 7.1 屏幕旋转流程分析]知道实际的旋转由WindowManagerService来完成,这里接着上面具体详细展开. 调了三个函数完成了三件事,即首先调用update ...
- Gradle之Android Gradle Plugin 主要流程分析(二)
[Android 修炼手册]Gradle 篇 -- Android Gradle Plugin 主要流程分析 预备知识 理解 gradle 的基本开发 了解 gradle task 和 plugin ...
- Android 4.4 音量调节流程分析(二)
之前在Android 4.4 音量调节流程分析(一)里已经有简单的分析音量控制的流程,今天想接着继续分析下音量大小计算的方法.对于任一播放文件而言其本身都有着固定大小的音量Volume_Max,而在A ...
- MSM8909中LK阶段LCM屏适配与显示流程分析(二)
1.前言 在前面的文章MSM8909中LK阶段LCM屏适配与显示流程分析(一),链接如下: https://www.cnblogs.com/Cqlismy/p/12019317.html 介绍了如何使 ...
- Uboot启动流程分析(二)
1.前言 在前面的文章Uboot启动流程分析(一)中,链接如下: https://www.cnblogs.com/Cqlismy/p/12000889.html 已经简单地分析了low_level_i ...
- Netty执行流程分析与重要组件介绍
一.环境搭建 创建工程,引入Netty依赖 二.基于Netty的请求响应Demo 1.TestHttpServerHandle 处理器.读取客户端发送过来的请求,并且向客户端返回hello worl ...
- Nginx-HTTP之静态网页访问流程分析二
11. HTTP 阶段执行 下面会依次执行以下阶段: NGX_HTTP_SERVER_REWRITE_PHASE: 在将请求的 URI 与 location 表达式匹配前,修改请求的 URI (所谓重 ...
随机推荐
- POJ-1004-Finanical Management
Description Larry graduated this year and finally has a job. He's making a lot of money, but somehow ...
- Asp.Net Core 2.0 项目实战(9) 日志记录,基于Nlog或Microsoft.Extensions.Logging的实现及调用实例
本文目录 1. Net下日志记录 2. NLog的使用 2.1 添加nuget引用NLog.Web.AspNetCore 2.2 配置文件设置 2.3 依赖配置及调用 ...
- jquery validate 动态增加删除验证规则
增加规则示例: $('.class').rules('add',{ required: true, messages:{ required: '这是必填,请填写', } }); 删除规则示例: $(' ...
- Python3 的描述符--完整例子详细解释
##描述符类的例子,这个例子说明了描述符和被描述符之间的关系 ##摄氏温度 class Celsius(): ## 1 描述符类 def __init__(self,value = 26.0): ## ...
- PHP 引用是个坑,请慎用
去年我参加了很多次会议,其中八次会议里我进行了相关发言,这其中我多次谈到了 PHP 的引用问题,因为很多人对它的理解有所偏差.在深入讨论这个问题之前,我们先回顾一下引用的基本概念,明确什么是" ...
- RDD概念、特性、缓存策略与容错
一.RDD概念与特性 1. RDD的概念 RDD(Resilient Distributed Dataset),是指弹性分布式数据集.数据集:Spark中的编程是基于RDD的,将原始数据加载到内存变成 ...
- 《团队-Android手机便签-项目进度》
首先想提个小意见,结对编程那边还有些问题需要处理,这个时候就催团队进度是不是不太好,至少应该让我们把结对处理完是吧.但是作业终究是作业,布置了就得做,我们只得匆匆忙忙画了个界面,功能什么的根本没来得及 ...
- Beta阶段报告
Beta版测试报告 1. 在测试过程中总共发现了多少Bug?每个类别的Bug分别为多少个? BUG名 修复的BUG 不能重现的BUG 非BUG 没能力修复的BUG 下个版本修复 url乱码 √ 手机端 ...
- 团队作业7——第二次项目冲刺(Beta版本12.08)
项目每个成员的进展.存在问题.接下来两天的安排. 已完成的内容:完成了排行榜的测试.上传头像功能的原型设计.界面优化 计划完成的内容:上传头像功能开发.测试.头像裁剪原型设计 每个人的工作 (有wor ...
- 201621123050 《Java程序设计》第1周学习总结
1.本周学习总结 java历史概述 java特点:1.简单 2.面向对象 3.健壮 4.跨平台 5.类库众多 JDK.JRE.JVM JDK:JAVA 开发工具包 ,包含JRE JRE: JAVA运行 ...