1.DispatcherServlet作用说明

  DispatcherServlet提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:

1、文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;

2、通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);

3、通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);

4、通过ViewResolver解析逻辑视图名到具体视图实现;

5、本地化解析;

6、渲染具体的视图等;

7、如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。

2.具体实现说明

看着上面的总结肯定知道了大体上的处理过程,但是对于实际的逻辑实现肯定是一头雾水,DispatcherServlet的主要处理是在doDispatch()方法中实现的。看看具体的代码实现:

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.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
} // Determine handler adapter for the current request.
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;
}
} if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
} // Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) {
return;
} applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

  2.1请求类型处理

  首先看代码:processedRequest = checkMultipart(request);就是为了处理文件类型的请求,如果请求类型是multipart将其转化通过MultipartResolver进行文件上传解析;如果不是就返回原来的请求。

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
"this typically results from an additional MultipartFilter in web.xml");
}
else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) {
logger.debug("Multipart resolution failed for current request before - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
return this.multipartResolver.resolveMultipart(request);
}
}
// If not returned before: return original request.
return request;
}

  首先判断是不是MultipartHttpServletRequest请求,如果是就不需要再处理了,如果是这继续判断是否含有文件解析失败异常,如果没有最后才会调用MultipartResolver接口实现类的resolveMultipart()方法来具体处理这个请求并且最后返回MultipartHttpServletRequest。当然了如果不是一个文件类型的请求就会直接的返回原始请求。

2.2 根据请求获取能够处理该请求的HandlerExecutionChain

doDispatch()方法中的mappedHandler = getHandler(processedRequest);完成了HandlerExecutionChain(处理器运行链,由处理器和处理器拦截器组成)的寻找定位工作。看下如何具体获取HandlerExecutionChain的过程:

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
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;
}

  这个过程就和SpringMVC处理化的过程有关了,SpringMVC初始化中通过initHandlerMappings(ApplicationContext context)方法初始化了List<HandlerMapping> handlerMappings;也就是通过将所有配置的请求映射放到了这个handlerMappings。这个方法的处理过程简单就是循环遍历这个handlerMappings,如果找到了就返回具体的HandlerMapping 中的HandlerExecutionChain,否则就返回null.如果返回的处理器为空,说明没有能够处理这个请求的 HandlerMapping,那么就会调用noHandlerFound(processedRequest, response)进行处理,看下noHandlerFound(processedRequest, response)具体的工作。

	protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
if (this.throwExceptionIfNoHandlerFound) {
ServletServerHttpRequest sshr = new ServletServerHttpRequest(request);
throw new NoHandlerFoundException(
sshr.getMethod().name(), sshr.getServletRequest().getRequestURI(), sshr.getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}

  设置HTTP响应状态,并且抛出异常。

2.3 获取请求对应的处理器适配器

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());决定了获取请求对应的处理器适配器。看下具体的实现:

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
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");
}

  和获取请求的HandlerExecutionChain对象一样,遍历初始化就完成的handlerAdapters,有返回没有则抛出异常。

2.4 GET 和 HEAD请求处理

对于请求方式是GET或者HEAD的,会判断是否发生改变,如果没有改变就直接返回,否则就继续下去

2.5 调用具体的HandlerAdapter处理请求

方法ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;是使用给定的处理器来处理这个请求,并且返回一个ModelAndView 。以SimpleServletHandlerAdapter为例,其实是调用一个Servlet的Service方法来进行相应的业务逻辑的处理的。

	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception { ((Servlet) handler).service(request, response);
return null;
}

  

2.6返回视图的再处理

对上面处理后的视图或者异常转化成一个ModelAndView对象:

	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { boolean errorView = false; if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
} // Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
} if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
} if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}

  如果有异常则将异常转化成ModelAndView,如果视图不为空,则调用render(mv, request, response);进行视图的解析渲染。

	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale); View view;
if (mv.isReference()) {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
} // Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}

  首先获取请求的本地化处理并且将其设置到response中,如果指定了视图名字那么DispatcherServlet就会调用相应的视图解析器来进行解析,返回View对象

	protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception { for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}

  如果ModelAndView 没有视图引用说明,它已经含有了一个真实的View对象直接过去即可。如果获取的View对象为空则需要爆出异常。最后也是最重要的就是调用View对象的render方法( view.render(mv.getModelInternal(), request, response))进行视图的渲染了。看具体实现是在View的实现抽象类中AbstractView中。

	public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
} Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

  简单的说就是获取数据并且填充,打完收工。

3.总结

  上面便是按照DispatcherServlet从接受到一个请求到请求完全处理完成所经历的一个过程,很是清晰明确。设计也很简单。从设计到实现,可以察觉设计模式的几大原则和具体的设计都有涉及,从中也学到了很多东西,接着学习,加油。

Spring之SpringMVC前端控制器DispatcherServlet(源码)分析的更多相关文章

  1. 涨姿势:Spring Boot 2.x 启动全过程源码分析

    目录 SpringApplication 实例 run 方法运行过程 总结 上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入 ...

  2. Spring 循环引用(二)源码分析

    Spring 循环引用(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 循环引用相关文章: & ...

  3. Spring Boot REST(二)源码分析

    Spring Boot REST(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) SpringBoot RE ...

  4. Spring Boot 2.x 启动全过程源码分析

    Spring Boot 2.x 启动全过程源码分析 SpringApplication 实例 run 方法运行过程 上面分析了 SpringApplication 实例对象构造方法初始化过程,下面继续 ...

  5. Spring Boot Dubbo 应用启停源码分析

    作者:张乎兴 来源:Dubbo官方博客 背景介绍 Dubbo Spring Boot 工程致力于简化 Dubbo | grep tid | grep -v "daemon" tid ...

  6. Spring Boot 2.x 启动全过程源码分析(上)入口类剖析

    Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boot 为什么这么简便的奥秘. 本篇基于 Spring Boot 2.0.3 版本进 ...

  7. Spring注解之@Lazy注解,源码分析和总结

    一 关于延迟加载的问题,有次和大神讨论他会不会直接或间接影响其他类.spring的好处就是文档都在代码里,网上百度大多是无用功. 不如,直接看源码.所以把当时源码分析的思路丢上来一波. 二 源码分析 ...

  8. 【Spring】DispatcherServlet源码分析

    使用过HttpServlet的都应该用过其doGet和doPost方法,接下来看看DispatcherServlet对这两个方法的实现(源码在DispatcherServlet的父类Framework ...

  9. springmvc工作原理以及源码分析(基于spring3.1.0)

    springmvc是一个基于spring的web框架.本篇文章对它的工作原理以及源码进行深入分析. 一.springmvc请求处理流程 二.springmvc的工作机制 三.springmvc核心源码 ...

随机推荐

  1. Excel2016右键新建工作表,打开时提示“因为文件格式或文件扩展名无效。请确定文件未损坏,并且文件扩展名与文件的格式匹配。”的解决办法

    上午新建excel工作表时,发现新建完之后居然打不开 提示: 尼玛这坑爹的,难道我的Excel坏了?? 排查问题之后发现 只有新建“Microsoft Excel 工作表”时会出现这种问题,新建“Wo ...

  2. openStack reboot调试

  3. JAVA插入数据到MySql少了8小时

    这个问题,真的是找了很久. 之前以为是mysql的timezone有问题.根据网上的方法,把timezone改了,还是不起作用. 然后以为是java的问题,但试来试去java也不存在timezone不 ...

  4. MVC3、如何应用EntityFramework 连接MySql 数据库

    原文:MVC3.如何应用EntityFramework 连接MySql 数据库 新的一年,新的开始. 今天总结的主题是在MySql中应用EntityFramework 的Code First模式. 开 ...

  5. 用overflow-y 解决web页面抖动问题

    页面抖动(左右抖动)让人视觉上很不爽.. /** original : php攻城师 http://blog.csdn.net/phpgcs **/ 最开始我也以为是 layout 不一致的原因..后 ...

  6. 关于PHP函数的操作

    <?php//简单函数function show(){    echo "hello";    }show(); //有参数的函数function show($a){     ...

  7. Struts2学习笔记(五)——Action访问Servlet API

    在Strut2中访问Servlet API有三种方式: 1.通过ActionContext访问Servlet API,推荐使用这种,但是这种方案它获取的不是真正的事Servlet API. 步骤: 1 ...

  8. 以太坊之——golang以太坊接口调用

    Go语言具有简单易学.功能强大,可跨平台编译等众多优势,所以这里选择以Go语言切入以太坊. 开始之前需要以下环境: Ubuntu(这里以ubuntu16.04为例) geth Ubuntu16.04安 ...

  9. 无可奈何的开始了jquery的“奇淫技巧”

    转载请注明出处: https://home.cnblogs.com/u/zhiyong-ITNote/ 修改一个已有的项目,主要是前端方面,一般的项目后端都是处理好了的,不需要改也不能改,除非特殊需求 ...

  10. 【转】Spring Boot干货系列:(六)静态资源和拦截器处理

    前言 本章我们来介绍下SpringBoot对静态资源的支持以及很重要的一个类WebMvcConfigurerAdapter. 正文 前面章节我们也有简单介绍过SpringBoot中对静态资源的默认支持 ...