从源码的角度彻底搞懂 HandlerMapping 和 HandlerAdapter
彻底搞懂 HandlerMapping和HandlerAdapter
知识点的回顾:
当Tomcat接收到请求后会回调Servlet的service方法,一开始入门Servlet时,我们会让自己的Servlet去实现HttpServlet接口,重写它的doGet()
和doPost()
方法
在SpringMvc中,SpringMvc的核心组件DispatcherSerlvet的继承图如上,可以看到上图,其实这个DispatcherServlet终究还是一个Servlet
我们追踪一下他的生命周期创建过程, 首先是说Servlet的创建时机,其实是存在两种情况的, 这取决于.setLoadOnStartup(1);
设置的启动级别,当然一般都会设置成正数,表示当容器启动时实例化Servlet
于是Tomcat实例化Servlet,Servlet被初始化时首先被回调的方法是init()
这大家都知道的,但是SpringMvc提供的DispatcherServlet中存在一个静态块,源码如下: 这个静态块干了什么事呢? 读取的是class path
下面的 DispatcherServlet.properties
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
// todo 它读取的是class path 下面的 DispatcherServlet.properties 配置文件
// todo resource/web/servlet/DispatcherServlet.properties
// todo 将这些默认的实现信息,封装进了Properties defaultStrategies
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
那问题来了,这个配置文件到底存放的什么? 让DispatcherServlet
如此迫切的去加载? 我们文件贴在下面,可以看到存放的是一些全类名,这些是DiapacherServlet针对不同策略接口提供的八个默认的实现,当在上下文中没有匹配到程序员添加的这些实现时,就会使用这些默认的实现
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
# todo 这里存在 DiapacherServlet策略接口的八个默认的实现
# todo 当在上下文中没有匹配到程序员添加的这些实现时,就会使用这些默认的实现
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
好吧, 虽然这也不算跑题,但是我们还是回到DispatcherServlet
的init()
方法,其实去这个DispatcherServlet
中是找不到这个init()
方法的, 那这个方法在哪里了呢? 其实就在他的祖父类HttpServletBean
中,源码如下:
意图很明确,前面用来初始化环境参数,后者调用 initServletBean();
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
// todo 设置初始化参数
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
// todo 初始化SerlvetBean
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
initServletBean();
见名知意,初始化ServletBean
,说白了就是想去初始化DispatcherServlet
呗,跟进去查看,不出意料,他是个抽象方法,跟进它的实现类
他的实现类是FrameworkServlet
,进去跟进,看他去创建应用的上下文,但是如果上下文已经被初始化了,他是不会重复创建上下文的
我们继续跟进它的onRefresh()
方法,同样这个方法是一个抽象方法,而它是实现就是我们关注的DispatcherServlet
,源码如下: 在下面做了很多事,我们还是仅仅关注两点,初始化了handlerMapping
和handlerAdapater
protected void onRefresh(ApplicationContext context) {
// todo 进行跟进,方法就在下面
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
// todo 初始化和文件上传相关的解析器
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
/**
* todo 初始化处理器映射器,这是我们看到重点
* todo 什么是处理器映射器?
* todo Controller在Spring中有多种情况, 那当一个用户的请求到来时, 如何进一步找到哪一种Controller来处理用户的请求呢?
* todo 这一步就是通过处理器映射器完成的
* todo 说白了, 通过处理器映射器我们可以找到那个处理当前请求的特定的组件, 我们称它为handler
* todo 但是这之间存在一个问题,就是说,这个handler到底是方法级别的,还是类级别的我们是不知道的,只能说,这个handler就肯定能处理这次请求而已
*/
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
到现在为止,本位关注的两个组件其实就完成了初始化了, 下一个问题就来了,什么时候使用他们呢?
那就得从Servlet的service()
方法说起了,大家都知道这个方法会在Serlvet每一次收到请求时就会被回调一次,再回想我们原来是怎么变成来着? 但是后来我们都直接实现HttpServlet
接口,然后重写他们的doGet()
和doPost()
来实现我们自己的Servlet, 那SpringMvc是怎么做的呢?
源码如下:
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
都是通过processRequest(request, response);
来实现的,我们往下追踪这个方法,最终也是不出所料,我们来到了DispacherServlet
中
我截取部分源码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
//异步编程
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//检查请求中是否存在文件上传的操作
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//todo 确定当前请求的处理程序,跟进去
//todo 换句话说就是 推断Controller的类型, Controller存在三种类型
// todo 跟进去看看这个handlerMapping的获取方式
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// todo 用到了适配器设计模式,
// todo 如果 上面的HandlerExecutionChain 是bean类型, 经过这个方法后将被设置成bean
// todo 如果 上面的HandlerExecutionChain 是 method 类型, 经过这个方法后将被设置成吗method
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
同样的,我们只是关注上面的两点,看看SpringMvc是如何玩转HandlerMapping
和HandlerAdapter
的, 分两步,先跟进 mappedHandler = getHandler(processedRequest);
方法
看看他干啥了,遍历所有的HandlerMapping
第一个问题: 还记不记得哪里来的RequestMappingHandlerMapping
,没错就是文章一开始我们去看初始化DispatcherServlet
时在静态块里面完成的加载已经后续的初始化,所以按理说,下面的数组中就存在两个handlerMapping
分别是BeanNameUrlHandlerMapping
和RequestMappingHandlerMapping
第二个问题: 什么是HandlerMapping
? 直接看它的中文翻译就是处理器映射器是不好理解的,其实也没有特别难懂, 就是一个映射器嘛,映射什么呢? 就是映射用户的请求与后端程序员编写的Controller之间的关系, 再直白一点, 就是一个用户的请求经过了这个HandlerMapping
就可以百分百确定出哪一个控制器是用来处理它的
第三个问题: 下面的HandlerMapping
是个数组,意味着这个映射的规则是多种多样的,所以来个循环,如果没有任何一个映射器满足条件怎么办呢? 404呗
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;
}
于是经过了上面HandlerMapping
的处理我们获取出来了一个 HandlerExecutionChain ,并且我们百分百确定这个 HandlerExecutionChain 就是用来处理当前的请求的,但是!!! 我们却不能直接使用,因为我们是不清楚前面的获取到的这个执行器链是个方法,还是个类,于是适配器就用上了
源码如下:
这个适配器就是HandlerAdapter
,使用设配器设计模式,不管得到的handler到底是什么类型的,都可以找到正确的方法区执行它
HandlerAdapter
同样是DisapcherServlet
的一大组件,它和上面的处理器映射器是一样的,同样是从配置文件中被读取出来
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
咋样? 其实本文倒是也没什么难度,就是觉得确实挺好玩的...
从源码的角度彻底搞懂 HandlerMapping 和 HandlerAdapter的更多相关文章
- Spring第四天,BeanPostProcessor源码分析,彻底搞懂IOC注入及注解优先级问题!
- Android AsyncTask完全解析,带你从源码的角度彻底理解
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进 ...
- [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...
- 从源码的角度分析ViewGruop的事件分发
从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...
- 从源码的角度解析View的事件分发
有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图 ...
- 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...
- 从源码的角度解析Mybatis的会话机制
坐在我旁边的钟同学听说我精通Mybatis源码(我就想不通,是谁透漏了风声),就顺带问了我一个问题:在同一个方法中,Mybatis多次请求数据库,是否要创建多个SqlSession会话? 可能最近撸多 ...
- [学习总结]7、Android AsyncTask完全解析,带你从源码的角度彻底理解
我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴 ...
- 从源码的角度看Activity是如何启动的
欢迎访问我的个人博客,原文链接:http://wensibo.top/2017/07/03/Binder/ ,未经允许不得转载! 大家好,今天想与大家一起分享的是Activity.我们平时接触的最多的 ...
随机推荐
- SqlServer 2014 还原数据库时提示:操作系统返回了错误5,,拒绝访问
场景 在进行数据库还原时提示: System.Data.SqlError:在对”“尝试”“时,操作系统返回了错误5(拒绝访问) 实现 第一种方案是修改要还原的数据库备份文件的权限. 找到备份文件右击属 ...
- 设置composer镜像地址为阿里云的方法
所有项目都会使用该镜像地址: composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ 取消配置: ...
- LinkedHashSet集合
LinkedHashSet集合与HashSet集合的最大区别在于,LinkedHashSet集合存入和取出的顺序相同,而HashSet集合存取顺序不一定相同: import java.util.Has ...
- 谈谈HTTPS安全认证,抓包与反抓包策略
文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则,超文本传输协议(HTTP)是一种通信 ...
- python+selenium六:隐式等待
python+selenium六:隐式等待 # 隐式等待 # 全局生效,只写一次即可(仅当前页面)# 若有页面切换,需sleep等待新页面出现后,再使用此方法 # 如:在35秒内,等待操作完成,完 ...
- IPMI在linux下常用命令
ipmitool lan print 1 ipmitool lan set 1 ipaddr 192.168.0.12 ipmitool lan set 1 netmask 255.255.255.0 ...
- 25 (OC)* iOS网络HTTP、TCP、UDP、Socket 知识总结
应用层:1.用户接口.应用程序:2.Application典型设备:网关:3.典型协议.标准和应用:TELNET.FTP.HTTP 表示层:1.数据表示.压缩和加密presentation2.典型设备 ...
- ASP.NET Core 3.0 使用gRPC
一.简介 gRPC 是一个由Google开源的,跨语言的,高性能的远程过程调用(RPC)框架. gRPC使客户端和服务端应用程序可以透明地进行通信,并简化了连接系统的构建.它使用HTTP/2作为通信协 ...
- [Scala]集合中List元素转Tuple元素的函数迭代写法
____ 本文链接: https://www.cnblogs.com/senwren/p/Scala-Lis-2-Tuple.html —— Scala没有提供相应写法, 但迭代写法仍然可以做到. 有 ...
- 一个js破解教程
很好的一篇文章,先存着以后用到. 为了防止官网更新修复,存一下版本 https://pan.lanzou.com/b220073/ 密码:这是秘密 这篇文章以 JavaScript 为例讲解了破解的一 ...