对于有教训的SpringMVC应用人员来说,应该大抵理解它对HTTP申请的解决流程,SpringMVC是对原生的Servlet的一种扩大,外面对申请的办法及解决申请的办法做了映射,也就是咱们常见的@RequestMapping中指定的门路,以及带有@RequestMapping注解的办法的映射。这个映射是在SpringMVC容器启动的时候预解析并初始化到Spring容器中的,他们保留在一个Map中,Key能够简略了解为申请门路,Value能够简略了解为申请处理器,也就是对应的controller办法。申请来了之后,则依据申请uri获取对应的处理器办法,而后反射调用。
那么SpringMVC的细节解决流程又是什么呢?咱们上面会逐渐从源码来理解其细节:
首先是初始化的过程,如何在Spring容器启动过程中去初始化控制器Controller和申请映射器RequestMapping,这个初始化流程实现之后,产生的后果就是将申请映射RequestMapping和处理器办法的对应关系注册到申请映射器注册核心MappingRegistry中。那么当http申请达到SpringMVC框架之后,它是如何来接管申请的呢?
上面来剖析一下!
首先在剖析之前,大家须要有一个基础知识储备,就是Servlet的执行流程,咱们都晓得SpringMVC框架是遵循Servlet标准,通过Servlet来扩大进去的,那么他的执行流程也肯定离不开原生的Servlet。
在Servlet容器「通常指Tomcat」中,每当Http申请达到Servlet容器之后,都会执行Servlet的service办法,那执行Servlet的service办法和SpringMVC执行流程有什么关系呢?
咱们首先来看看SpringMVC的外围处理器DispatcherServlet的继承关系图,如下:
能够看到最上层的父类就是Servlet的子类实现HttpServlet,那么来看看这个service办法是在哪调用的呢?
依照之前介绍的技巧,能够从最底层的子类一个一个往上找,看看这个init办法是在哪个类中调用的,找完之后,发现是在FrameworkServlet中调用的,如下:
/**
* 重写了父类的办法,减少对PATCH申请形式的解决
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) {
// 获取申请办法类型。将申请办法类型转换为HttpMethod枚举类型
HttpMethod httpMethod = HttpMethod.resolve(req.getMethod());
// 如果是PATCH类型,间接走processRequest办法。
// PATCH申请:PUT申请办法是用来替换资源的,而PATCH办法是用来更新局部资源的.
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(req, resp);
}
else {
// 调用父类HttpServlet的service办法,父类办法中会判断申请类型是get还是post,从而去调用子类的doGet或者doPost等办法
super.service(req, resp);
}
}
第一步首先获取http申请的申请类型,能够略微理解理解这个办法,依据办法名称从一个Map汇合中获取申请类型,能够看到这个枚举类中有如下的8种枚举类型,也就是HTTP的申请形式有8种。
public enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
}
如果是PATCH类型的申请,间接走processRequest办法,那么这个PATCH申请是个什么玩意儿呢?
咱们都晓得,RestFULL设计规范中,PUT申请是用来替换资源的,而这个PATCH申请则是用来更新局部资源的。
接着看super.service办法,如果不是PATCH申请而且Method存在的话,就会执行这个办法,这个办法点开之后,间接调用的是HttpServlet的service办法,这个办法中会依据申请类型POST或者GET等来决定调用doGet、doPost还是其余类型的办法。能够看到,此处是用来了分派设计模式。
这个办法中,假如调用的是一个GET形式申请的接口,那么就会调用doGet办法,而这个doGet办法又被子类FrameworkServlet覆写了,所以会调用到FrameworkServlet的doGet办法,咱们来看看FrameworkServlet的doGet办法,如下:
/**
* 解决get申请,异样省略掉了
*/
@Override
protected final void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 解决get申请
processRequest(req, resp);
}
外面间接调用了processRequest办法,和方才下面看到的解决PATCH申请调用的办法一样,那么持续剖析processRequest办法,这个办法的外围代码如下:
/**
* 解决具体的http申请,异样省略掉
*/
protected final void processRequest(HttpServletRequest req,
HttpServletResponse resp) {
// 两头解决国际化和异步调用的代码临时先省略掉了
try {
// 调用doService办法来解决申请
doService(req, resp);
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
// 重置ContextHolder
...
}
}
能够看到,外围就是调用了一个doService办法。咱们在看doService办法的实现,发现它是一个形象办法,没有实现,有一次看到了模板设计模式的利用了吧?咱们找其子类中对这个办法的实现,发现是在DispatcherServlet中实现的,外围代码如下:
@Override
protected void doService(HttpServletRequest req, HttpServletResponse resp) {
// 打印申请日志
logRequest(req);
// 设置了一堆解析器,也就是Spring的九大组件
// 能够看到,设置到request中,后续用到,间接从request中获取
req.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
req.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
req.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
req.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
req.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
// 调用doDispatch办法来解决申请,SpringMVC用来解决申请的外围办法.
doDispatch(req, resp);
}
finally {
// 省略
}
}
外围代码还是一行,即调用doDispatch办法,这个办法就到SpringMVC真正的外围解决办法了,所有的解决逻辑都是在这个办法中实现的,包含文件上传、拦截器、异样处理器等。咱们先看外围解决申请的脉络,具体的其余性能能够缓缓理解,外围逻辑代码如下,上面代码中为了看着清晰,删除了一些校验和变量定义相干的代码:
protected void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
HttpServletRequest processedRequest = req;
// 处理器执行链
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
try {
try {
// 查看申请中是否有二进制流,用来判断申请是否为文件上传申请。
// 如果容器中没有配置MultipartResolver组件,则间接疏忽文件上传申请
processedRequest = checkMultipart(req);
// 须要解决文件上传申请
multipartRequestParsed = (processedRequest != req);
// 依据申请查找申请处理器
// 申明一个controller通常有三种办法:
// (1)@Controller
// (2)实现HttpRequestHandler接口,并覆写handleRequest办法,
将controller类通过@Component标注为Bean
// (3)通过xml配置.
mappedHandler = getHandler(processedRequest);
// 如果没有找到对应的handler,间接返回404
if (mappedHandler == null) {
noHandlerFound(processedRequest, resp);
return;
}
// 查找以后申请处理器对应的适配器,通常应用的是RequestMappingHandlerAdaptor,其余两种Adaptor个别不罕用
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 解决申请之前,执行拦截器中配置的办法,获取到配置的所有拦截器,循环遍历,顺次执行
// 如果拦截器执行过程中返回了false,则间接返回,不会再执行指标办法
if (!mappedHandler.applyPreHandle(processedRequest, resp)) {
return;
}
// 执行SpringMVC中实在对应的业务办法
// HandlerAdapter的实现子类有:AbstractHandlerMethodAdapter、HttpRequestHandlerAdapter
mv = ha.handle(processedRequest, resp, mappedHandler.getHandler());
// 查找视图门路. prefix + uri + suffix
applyDefaultViewName(processedRequest, mv);
// 解决申请之后执行拦截器办法.
mappedHandler.applyPostHandle(processedRequest, resp, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
// 将解决实现之后的后果进行页面视图渲染。比方:跳转到Jsp页面.
processDispatchResult(processedRequest, resp, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
// 页面渲染过程中,如果出现异常,则会间接执行afterCompletion办法
triggerAfterCompletion(processedRequest, resp, mappedHandler, ex);
}
finally {
// 一些善后工作,例如清理文件上传留下来的临时文件等
}
}
上述办法执行实现之后,SpringMVC的一次申请就解决实现了。因为篇幅起因,具体的文件上传判断、视图渲染返回、异样解决、拦截器执行逻辑前面文章继续介绍。
带有正文的源码曾经上传到github,地址:https://github.com/wb02125055…
能够自行fork。有问题下方留言!
发表回复