浅谈对Spring mvc的理解和DispatherServlet源码深入分析
2017-09-19 20:54
579 查看
简介
Spring mvc是Spring 为展示层提供的一个优秀的Web框架,它基于MVC的设计理念,并采用松耦合、可插拔的组件结构,让它比其他的MVC框架更具有灵活性和扩展性。体系结构
学习Spring MVC的时候,最重要还是要懂得它里面内部的各个组件是如何运转工作的,我刚开始接触的时候,只想着一门心思去写代码,而忽略了其最精髓的原理理解和逻辑流程,这也导致我在进行Spring mvc配置和编写代码的时候经常出现错误,反而大大地降低了我的开发效率,因此了解Sping mvc的工作流程、体系结构还是对我们非常有帮助的。下面是spring mvc的一个框架模型图:
Spring mvc请求处理过程详解:
(1)首先,客户端向服务器短发送一个HTTP请求,我们的应用服务器接收带这个请求后,先判断是否匹配DispatcherServlet的请求路径映射(这是在配置web.xml的时候指定),匹配成功则进入Spring mvc 的核心组件DispatcherServlet中进行处理
(2)DispatcherServlet接受到请求后,将根据请求信息以及HadlerMapping的路径配置找到处理该请求的处理器Handler(也就是我们写得Controller),在这个 过程中,HandlerMapping相当于一个路由控制器,帮助我们去找到“目标主机”Handler
( 3 )找到Handler后,会通过适配器HandlerAdapter对Handler经行封装,并调用统一的适配器接口调用这个Handler.
(4)处理器完成了业务逻辑后,会返回一个ModelAndView对象给DispatcherServlet,这个对象包含了视图逻辑名和后期用来渲染页面的数据信息。
(5)DispatcherServlet得到这个ModelAndView对象后并不能直接地得到 视图对象,注意上面说地是逻辑视图名,而要获取真正的视图名,DispatcherServlet需要借助视图解析器ViewResolver通过解析得到真正的视图名,从而获得视图对象(比如jsp)
(6)之后DispatcherServlet使用之前的Model数据对这个视图View进行页面渲染。
(7)最后返回客户端,可以是html、pdf、xml、json等。
对DispatcherServlet的源码分析
DispatcherServlet可以说是Spring mvc最核心的一个组件,而Spring是如何将业务层的spring mvc的上下文组件装配在这个统一的DispatcherServlet中的呢?我们可以通过对DispatherServlet的源码分析找到答案,下面是DispatcherServler的一些代码,首先DispatcherServlet是继承FramerworkServlet类,而这个FrameworkServlet类又是继承的HttpServletBean,我们现对这个FrameworkServlet的源码中重要方法进行分析,如下:protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String method = request.getMethod(); if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) { //这一这里 processRequest(request, response); } else { super.service(request, response); } } @Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //注意这里 processRequest(request, response); } @Override protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); }
通过上面每个servlet的几个重要方法(service、do××),它们都依赖这个processRequest方法,而这个processRequest方法中主要依赖的是doService方法,这个doService方法是由protecte abstrac修饰,因此其具体的逻辑由其子类DispatcherServlet实现
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes); try { //注意这里 doService(request, response); } //省略一系列catch异常处理代码 finally { //。。。。省略代码 } }`
这是doService的源码:
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
对FrameworkServlet源码总结:FrameworkServlet 中覆盖了 HttpServlet 的 doGet(),doPost()等方法,而 doGet(),doPost()等又直接调用方法 processRequest 来处理请求,进入 processRequest 方法,实际的请求处理是调用其抽象方法 doService(由子类完成),进入DispatcherServlet源码后,我们可以看见在doService里面执行最后最主要的方法代码如下:
@Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : ""; logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]"); } // Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<String, 4000 Object>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } // Make framework objects available to handlers and view objects. request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); try { //注意这里 doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
下面是对doDispatch(request,response)源码:
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); } } } }
DispatcherServlet 中doService具体实现请求的处理分发,先是把一些资源放到请求属性中,然后调用 doDispatch 实现请求分发到控制器的 handler。doDispatch 中首先会判断是否是文件传输流的请求(利用MultipartResolver),如果是的话就会转为 MultipartHttpServletRequest。接下来 getHandler(processedRequest) 根据请求获得对应的handler,最后调用 handle() 处理请求,会反射到在控制器中实现的方法
好了,我们接着分析DispatcherServlet的initStrategies方法的源码来接之前提出的问题
protected void initStrategies(ApplicationContext context) { //初始化上传文件解析器 initMultipartResolver(context); //初始化本地化解析器 initLocaleResolver(context); initThemeResolver(context); //初始化处理映射器 initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); //初始化视图解析器 initViewResolvers(context); initFlashMapManager(context); }
因此我们可以得出结论,Spring 之所以能够将Spring mvc的组件组装到DispatcherServlet中是因为在WebAppicationContext执行后,DispatcherServlet会自动执行它里面的innitStrategies方法,而在这个方法里面,会自动地处理化一系列的组件。
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties"; /** Additional logger to use when no mapped handler is found for a request. */ protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY); private static final Properties defaultStrategies; static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage()); } }
这个静态代码块表明Spring mvc有一套默认的组件配置,如果我们不对这些组件进行显示地配置,那么这个DispatcherServlet就会初始化实现提供的默认组件配置,我们可以通过调用DispatcherServlet的getDefaultStrategies获取这个默认的组件配置信息。
相关文章推荐
- 深入理解Glide源码,分析之路(一):基本用法,史上最详细、易懂
- 深入理解Spark 2.1 Core (四):运算结果处理和容错的原理与源码分析
- 深入理解Spark 2.1 Core (十三):sparkEnv类源码分析
- 深入理解View知识系列一- setContentView和LayoutInflater源码原理分析
- 深入理解Spark 2.1 Core (一):RDD的原理与源码分析
- 深入理解Android事件分发机制之源码分析
- 【Android开发】深入理解硬盘缓存类DiskLruCache:源码分析
- 深入理解Spark 2.1 Core (十四):securityManager 类源码分析
- 深入理解Spark 2.1 Core (十一):Shuffle Reduce 端的原理与源码分析
- 深入理解Spark 2.1 Core (五):Standalone模式运行的原理与源码分析
- 深入理解Spring系列之十:DispatcherServlet请求分发源码分析
- 深入理解 spring 容器,源码分析加载过程
- 深入理解Spark 2.1 Core (十):Shuffle Map 端的原理与源码分析
- 深入理解Spark 2.1 Core (二):DAG调度器的原理与源码分析
- 分析CSLA.Net 4.* 开源框架的源码,深入理解框架内部运行机制
- 深入理解Spark ML:多项式朴素贝叶斯原理与源码分析
- 深入理解Spark 2.1 Core (六):资源调度的原理与源码分析
- 深入理解dubbo之服务发布源码分析
- 深入理解Spark 2.1 Core (三):任务调度器的原理与源码分析
- 随笔:深入理解HashMap——put和get方法的源码分析