SpringMVC源码分析1:SpringMVC概述

  1. 云栖社区>
  2. 博客>
  3. 正文

SpringMVC源码分析1:SpringMVC概述

黄小斜 2017-07-24 11:58:33 浏览461
展开阅读全文

Web MVC简介

1.1、Web开发中的请求-响应模型:

 

在Web世界里,具体步骤如下:

1、  Web浏览器(如IE)发起请求,如访问http://sishuok.com

2、  Web服务器(如Tomcat)接收请求,处理请求(比如用户新增,则将把用户保存一下),最后产生响应(一般为html)。

3、web服务器处理完成后,返回内容给web客户端(一般就是我们的浏览器),客户端对接收的内容进行处理(如web浏览器将会对接收到的html内容进行渲染以展示给客户)。

 

因此,在Web世界里:

都是Web客户端发起请求,Web服务器接收、处理并产生响应。

 

一般Web服务器是不能主动通知Web客户端更新内容。虽然现在有些技术如服务器推(如Comet)、还有现在的HTML5 websocket可以实现Web服务器主动通知Web客户端。

 

到此我们了解了在web开发时的请求/响应模型,接下来我们看一下标准的MVC模型是什么。

 

1.2、标准MVC模型概述

MVC模型:是一种架构型的模式,本身不引入新功能,只是帮助我们将开发的结构组织的更加合理,使展示与模型分离、流程控制逻辑、业务逻辑调用与展示逻辑分离。如图1-2

 

图1-2

首先让我们了解下MVC(Model-View-Controller)三元组的概念:

Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据) 和 服务层(行为)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。

 

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。

 

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作,。

 

从图1-1我们还看到,在标准的MVC中模型能主动推数据给视图进行更新(观察者设计模式,在模型上注册视图,当模型更新时自动更新视图),但在Web开发中模型是无法主动推给视图(无法主动更新用户界面),因为在Web开发是请求-响应模型。

 

那接下来我们看一下在Web里MVC是什么样子,我们称其为 Web MVC 来区别标准的MVC。

1.3、Web MVC概述

模型-视图-控制器概念和标准MVC概念一样,请参考1.2,我们再看一下Web MVC标准架构,如图1-3:

 

如图1-3

 

在Web MVC模式下,模型无法主动推数据给视图,如果用户想要视图更新,需要再发送一次请求(即请求-响应模型)。

 

概念差不多了,我们接下来了解下Web端开发的发展历程,和使用代码来演示一下Web MVC是如何实现的,还有为什么要使用MVC这个模式呢?

 

1.4、Web端开发发展历程

此处我们只是简单的叙述比较核心的历程,如图1-4

 

图1-4

1.4.1、CGI:(Common Gateway Interface)公共网关接口,一种在web服务端使用的脚本技术,使用C或Perl语言编写,用于接收web用户请求并处理,最后动态产生响应给用户,但每次请求将产生一个进程,重量级。

 

1.4.2、Servlet:一种JavaEE web组件技术,是一种在服务器端执行的web组件,用于接收web用户请求并处理,最后动态产生响应给用户。但每次请求只产生一个线程(而且有线程池),轻量级。而且能利用许多JavaEE技术(如JDBC等)。本质就是在java代码里面 输出 html流。但表现逻辑、控制逻辑、业务逻辑调用混杂。如图1-5    

 

图1-5

如图1-5,这种做法是绝对不可取的,控制逻辑、表现代码、业务逻辑对象调用混杂在一起,最大的问题是直接在Java代码里面输出Html,这样前端开发人员无法进行页面风格等的设计与修改,即使修改也是很麻烦,因此实际项目这种做法不可取。

 

1.4.3、JSP:(Java Server Page):一种在服务器端执行的web组件,是一种运行在标准的HTML页面中嵌入脚本语言(现在只支持Java)的模板页面技术。本质就是在html代码中嵌入java代码。JSP最终还是会被编译为Servlet,只不过比纯Servlet开发页面更简单、方便。但表现逻辑、控制逻辑、业务逻辑调用还是混杂。如图1-6

 

 

图1-6

 

如图1-6,这种做法也是绝对不可取的,控制逻辑、表现代码、业务逻辑对象调用混杂在一起,但比直接在servlet里输出html要好一点,前端开发人员可以进行简单的页面风格等的设计与修改(但如果嵌入的java脚本太多也是很难修改的),因此实际项目这种做法不可取。

 

JSP本质还是Servlet,最终在运行时会生成一个Servlet(如tomcat,将在tomcat\work\Catalina\web应用名\org\apache\jsp下生成),但这种使得写html简单点,但仍是控制逻辑、表现代码、业务逻辑对象调用混杂在一起。

 

1.4.4、Model1:可以认为是JSP的增强版,可以认为是jsp+javabean如图1-7

特点:使用<jsp:useBean>标准动作,自动将请求参数封装为JavaBean组件;还必须使用java脚本执行控制逻辑。

 

 

图1-7

此处我们可以看出,使用<jsp:useBean>标准动作可以简化javabean的获取/创建,及将请求参数封装到javabean,再看一下Model1架构,如图1-8。

 

图1-8 Model1架构

Model1架构中,JSP负责控制逻辑、表现逻辑、业务对象(javabean)的调用,只是比纯JSP简化了获取请求参数和封装请求参数。同样是不好的,在项目中应该严禁使用(或最多再demo里使用)。

 

1.4.5、Model2:在JavaEE世界里,它可以认为就是Web MVC模型

Model2架构其实可以认为就是我们所说的Web MVC模型,只是控制器采用Servlet、模型采用JavaBean、视图采用JSP,如图1-9

 

图1-9 Model2架构

 

具体代码事例如下:

 

 

从Model2架构可以看出,视图和模型分离了,控制逻辑和展示逻辑分离了。

但我们也看到严重的缺点:

1.  1、控制器:

1.1.1、控制逻辑可能比较复杂,其实我们可以按照规约,如请求参数submitFlag=toAdd,我们其实可以直接调用toAdd方法,来简化控制逻辑;而且每个模块基本需要一个控制器,造成控制逻辑可能很复杂;

1.1.2、请求参数到模型的封装比较麻烦,如果能交给框架来做这件事情,我们可以从中得到解放;

1.1.3、选择下一个视图,严重依赖Servlet API,这样很难或基本不可能更换视图;

1.1.4、给视图传输要展示的模型数据,使用Servlet API,更换视图技术也要一起更换,很麻烦。

 

1.2、模型:

1.2.1、此处模型使用JavaBean,可能造成JavaBean组件类很庞大,一般现在项目都是采用三层架构,而不采用JavaBean。

 

 

1.3、视图

1.3.1、现在被绑定在JSP,很难更换视图,比如Velocity、FreeMarker;比如我要支持Excel、PDF视图等等。

 

1.4.5、服务到工作者:Front Controller + Application Controller + Page Controller + Context

即,前端控制器+应用控制器+页面控制器(也有称其为动作)+上下文,也是Web MVC,只是责任更加明确,详情请参考《核心J2EE设计模式》和《企业应用架构模式》如图1-10:

 

图1-10

运行流程如下:

 

职责:

Front Controller:前端控制器,负责为表现层提供统一访问点,从而避免Model2中出现的重复的控制逻辑(由前端控制器统一回调相应的功能方法,如前边的根据submitFlag=login转调login方法);并且可以为多个请求提供共用的逻辑(如准备上下文等等),将选择具体视图和具体的功能处理(如login里边封装请求参数到模型,并调用业务逻辑对象)分离。

 

Application Controller:应用控制器,前端控制器分离选择具体视图和具体的功能处理之后,需要有人来管理,应用控制器就是用来选择具体视图技术(视图的管理)和具体的功能处理(页面控制器/命令对象/动作管理),一种策略设计模式的应用,可以很容易的切换视图/页面控制器,相互不产生影响。

 

Page Controller(Command):页面控制器/动作/处理器:功能处理代码,收集参数、封装参数到模型,转调业务对象处理模型,返回逻辑视图名交给前端控制器(和具体的视图技术解耦),由前端控制器委托给应用控制器选择具体的视图来展示,可以是命令设计模式的实现。页面控制器也被称为处理器或动作。

 

Context:上下文,还记得Model2中为视图准备要展示的模型数据吗,我们直接放在request中(Servlet API相关),有了上下文之后,我们就可以将相关数据放置在上下文,从而与协议无关(如Servlet API)的访问/设置模型数据,一般通过ThreadLocal模式实现。

 

 

到此,我们回顾了整个web开发架构的发展历程,可能不同的web层框架在细节处理方面不同,但的目的是一样的:

干净的web表现层:

    模型和视图的分离;

控制器中的控制逻辑与功能处理分离(收集并封装参数到模型对象、业务对象调用);

控制器中的视图选择与具体视图技术分离。

轻薄的web表现层:

    做的事情越少越好,薄薄的,不应该包含无关代码;

       只负责收集并组织参数到模型对象,启动业务对象的调用;

       控制器只返回逻辑视图名并由相应的应用控制器来选择具体使用的视图策略;

       尽量少使用框架特定API,保证容易测试。

 

到此我们了解Web MVC的发展历程,接下来让我们了解下Spring MVC到底是什么、架构及来个HelloWorld了解下具体怎么使用吧。

 

本章具体代码请参考 springmvc-chapter1工程。


2.1、Spring Web MVC是什么

Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的。

 

另外还有一种基于组件的、事件驱动的Web框架在此就不介绍了,如Tapestry、JSF等。

 

Spring Web MVC也是服务到工作者模式的实现,但进行可优化。前端控制器是DispatcherServlet;应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器管理和视图解析器(View Resolver)进行视图管理;页面控制器/动作/处理器为Controller接口(仅包含ModelAndView handleRequest(request, response) 方法)的实现(也可以是任何的POJO类);支持本地化(Locale)解析、主题(Theme)解析及文件上传等;提供了非常灵活的数据验证、格式化和数据绑定机制;提供了强大的约定大于配置(惯例优先原则)的契约式编程支持。

2.2、Spring Web MVC能帮我们做什么

√让我们能非常简单的设计出干净的Web层和薄薄的Web层;

√进行更简洁的Web层的开发;

√天生与Spring框架集成(如IoC容器、AOP等);

√提供强大的约定大于配置的契约式编程支持;

√能简单的进行Web层的单元测试;

√支持灵活的URL到页面控制器的映射;

√非常容易与其他视图技术集成,如Velocity、FreeMarker等等,因为模型数据不放在特定的API里,而是放在一个Model里(Map数据结构实现,因此很容易被其他框架使用);

√非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API;

√提供一套强大的JSP标签库,简化JSP开发;

√支持灵活的本地化、主题等解析;

√更加简单的异常处理;

√对静态资源的支持;

√支持Restful风格。

2.3、Spring Web MVC架构

Spring Web MVC框架也是一个基于请求驱动的Web框架,并且也使用了前端控制器模式来进行设计,再根据请求映射规则分发给相应的页面控制器(动作/处理器)进行处理。首先让我们整体看一下Spring Web MVC处理请求的流程:

 

2.3.1、Spring Web MVC处理请求的流程

如图2-1

 

图2-1

具体执行步骤如下:

1、  首先用户发送请求————>前端控制器,前端控制器根据请求信息(如URL)来决定选择哪一个页面控制器进行处理并把请求委托给它,即以前的控制器的控制逻辑部分;图2-1中的1、2步骤;

2、  页面控制器接收到请求后,进行功能处理,首先需要收集和绑定请求参数到一个对象,这个对象在Spring Web MVC中叫命令对象,并进行验证,然后将命令对象委托给业务对象进行处理;处理完毕后返回一个ModelAndView(模型数据和逻辑视图名);图2-1中的3、4、5步骤;

3、  前端控制器收回控制权,然后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染;图2-1中的步骤6、7;

4、  前端控制器再次收回控制权,将响应返回给用户,图2-1中的步骤8;至此整个结束。

 

问题:

1、  请求如何给前端控制器?

2、  前端控制器如何根据请求信息选择页面控制器进行功能处理?

3、  如何支持多种页面控制器呢?

4、  如何页面控制器如何使用业务对象?

5、  页面控制器如何返回模型数据?

6、  前端控制器如何根据页面控制器返回的逻辑视图名选择具体的视图进行渲染?

7、  不同的视图技术如何使用相应的模型数据?

 

首先我们知道有如上问题,那这些问题如何解决呢?请让我们先继续,在后边依次回答。

 

2.3.2、Spring Web MVC架构

1、Spring Web MVC核心架构图,如图2-2

 

图2-2

 

 

架构图对应的DispatcherServlet核心代码如下:

 

java代码:
Java代码  收藏代码
  1. //前端控制器分派方法  
  2. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  
  3.         HttpServletRequest processedRequest = request;  
  4.         HandlerExecutionChain mappedHandler = null;  
  5.         int interceptorIndex = -1;  
  6.   
  7.         try {  
  8.             ModelAndView mv;  
  9.             boolean errorView = false;  
  10.   
  11.             try {  
  12.                    //检查是否是请求是否是multipart(如文件上传),如果是将通过MultipartResolver解析  
  13.                 processedRequest = checkMultipart(request);  
  14.                    //步骤2、请求到处理器(页面控制器)的映射,通过HandlerMapping进行映射  
  15.                 mappedHandler = getHandler(processedRequest, false);  
  16.                 if (mappedHandler == null || mappedHandler.getHandler() == null) {  
  17.                     noHandlerFound(processedRequest, response);  
  18.                     return;  
  19.                 }  
  20.                    //步骤3、处理器适配,即将我们的处理器包装成相应的适配器(从而支持多种类型的处理器)  
  21.                 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  
  22.   
  23.                   // 304 Not Modified缓存支持  
  24.                 //此处省略具体代码  
  25.   
  26.                 // 执行处理器相关的拦截器的预处理(HandlerInterceptor.preHandle)  
  27.                 //此处省略具体代码  
  28.   
  29.                 // 步骤4、由适配器执行处理器(调用处理器相应功能处理方法)  
  30.                 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  
  31.   
  32.                 // Do we need view name translation?  
  33.                 if (mv != null && !mv.hasView()) {  
  34.                     mv.setViewName(getDefaultViewName(request));  
  35.                 }  
  36.   
  37.                 // 执行处理器相关的拦截器的后处理(HandlerInterceptor.postHandle)  
  38.                 //此处省略具体代码  
  39.             }  
  40.             catch (ModelAndViewDefiningException ex) {  
  41.                 logger.debug("ModelAndViewDefiningException encountered", ex);  
  42.                 mv = ex.getModelAndView();  
  43.             }  
  44.             catch (Exception ex) {  
  45.                 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);  
  46.                 mv = processHandlerException(processedRequest, response, handler, ex);  
  47.                 errorView = (mv != null);  
  48.             }  
  49.   
  50.             //步骤5 步骤6、解析视图并进行视图的渲染  
  51. //步骤5 由ViewResolver解析View(viewResolver.resolveViewName(viewName, locale))  
  52. //步骤6 视图在渲染时会把Model传入(view.render(mv.getModelInternal(), request, response);)  
  53.             if (mv != null && !mv.wasCleared()) {  
  54.                 render(mv, processedRequest, response);  
  55.                 if (errorView) {  
  56.                     WebUtils.clearErrorRequestAttributes(request);  
  57.                 }  
  58.             }  
  59.             else {  
  60.                 if (logger.isDebugEnabled()) {  
  61.                     logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +  
  62.                             "': assuming HandlerAdapter completed request handling");  
  63.                 }  
  64.             }  
  65.   
  66.             // 执行处理器相关的拦截器的完成后处理(HandlerInterceptor.afterCompletion)  
  67.             //此处省略具体代码  
  68.   
  69.   
  70.         catch (Exception ex) {  
  71.             // Trigger after-completion for thrown exception.  
  72.             triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);  
  73.             throw ex;  
  74.         }  
  75.         catch (Error err) {  
  76.             ServletException ex = new NestedServletException("Handler processing failed", err);  
  77.             // Trigger after-completion for thrown exception.  
  78.             triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);  
  79.             throw ex;  
  80.         }  
  81.   
  82.         finally {  
  83.             // Clean up any resources used by a multipart request.  
  84.             if (processedRequest != request) {  
  85.                 cleanupMultipart(processedRequest);  
  86.             }  
  87.         }  
  88.     }  

 

核心架构的具体流程步骤如下:

1、  首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;

2、  DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;

3、  DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;

4、  HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);

5、  ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;

6、  View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;

7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

 

此处我们只是讲了核心流程,没有考虑拦截器、本地解析、文件上传解析等,后边再细述。

 

到此,再来看我们前边提出的问题:

 

 

1、  请求如何给前端控制器?这个应该在web.xml中进行部署描述,在HelloWorld中详细讲解。

2、  前端控制器如何根据请求信息选择页面控制器进行功能处理? 我们需要配置HandlerMapping进行映射

3、  如何支持多种页面控制器呢?配置HandlerAdapter从而支持多种类型的页面控制器

4、  如何页面控制器如何使用业务对象?可以预料到,肯定利用Spring IoC容器的依赖注入功能

5、  页面控制器如何返回模型数据?使用ModelAndView返回

6、  前端控制器如何根据页面控制器返回的逻辑视图名选择具体的视图进行渲染? 使用ViewResolver进行解析

7、  不同的视图技术如何使用相应的模型数据? 因为Model是一个Map数据结构,很容易支持其他视图技术

 

在此我们可以看出具体的核心开发步骤:

1、  DispatcherServlet在web.xml中的部署描述,从而拦截请求到Spring Web MVC

2、  HandlerMapping的配置,从而将请求映射到处理器

3、  HandlerAdapter的配置,从而支持多种类型的处理器

4、  ViewResolver的配置,从而将逻辑视图名解析为具体视图技术

5、处理器(页面控制器)的配置,从而进行功能处理

 

上边的开发步骤我们会在Hello World中详细验证。

 

2.4、Spring Web MVC优势

1、清晰的角色划分:前端控制器(DispatcherServlet)、请求到处理器映射(HandlerMapping)、处理器适配器(HandlerAdapter)、视图解析器(ViewResolver)、处理器或页面控制器(Controller)、验证器(   Validator)、命令对象(Command  请求参数绑定到的对象就叫命令对象)、表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。

2、分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要;

3、由于命令对象就是一个POJO,无需继承框架特定API,可以使用命令对象直接作为业务对象;

4、和Spring 其他框架无缝集成,是其它Web框架所不具备的;

5、可适配,通过HandlerAdapter可以支持任意的类作为处理器;

6、可定制性,HandlerMapping、ViewResolver等能够非常简单的定制;

7、功能强大的数据验证、格式化、绑定机制;

8、利用Spring提供的Mock对象能够非常简单的进行Web层单元测试;

9、本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。

10、强大的JSP标签库,使JSP编写更容易。

………………还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。



3.1、DispatcherServlet作用

DispatcherServlet是前端控制器设计模式的实现,提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。 具体请参考第二章的图2-1。

 

DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:

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

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

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

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

5、本地化解析;

6、渲染具体的视图等;

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

 

从以上我们可以看出DispatcherServlet主要负责流程的控制(而且在流程中的每个关键点都是很容易扩展的)。

 

3.2、DispatcherServlet在web.xml中的配置

    <servlet>
        <servlet-name>chapter2</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>chapter2</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
 

 

load-on-startup:表示启动容器时初始化该Servlet;

url-pattern:表示哪些请求交给Spring Web MVC处理, “/” 是用来定义默认servlet映射的。也可以如“*.html”表示拦截所有以html为扩展名的请求。

 

该DispatcherServlet默认使用WebApplicationContext作为上下文,Spring默认配置文件为“/WEB-INF/[servlet名字]-servlet.xml”。

 

DispatcherServlet也可以配置自己的初始化参数,覆盖默认配置:

摘自Spring Reference

参数

描述

contextClass

实现WebApplicationContext接口的类,当前的servlet用它来创建上下文。如果这个参数没有指定, 默认使用XmlWebApplicationContext。

contextConfigLocation

传给上下文实例(由contextClass指定)的字符串,用来指定上下文的位置。这个字符串可以被分成多个字符串(使用逗号作为分隔符) 来支持多个上下文(在多上下文的情况下,如果同一个bean被定义两次,后面一个优先)。

namespace

WebApplicationContext命名空间。默认值是[server-name]-servlet。

 

因此我们可以通过添加初始化参数

 

    <servlet>
        <servlet-name>chapter2</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-servlet-config.xml</param-value>
        </init-param>
    </servlet>

 

如果使用如上配置,Spring Web MVC框架将加载“classpath:spring-servlet-config.xml”来进行初始化上下文而不是“/WEB-INF/[servlet名字]-servlet.xml”。

3.3、上下文关系

集成Web环境的通用配置:

<context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>
          classpath:spring-common-config.xml,
          classpath:spring-budget-config.xml
      </param-value>
</context-param>
<listener>  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

 

如上配置是Spring集成Web环境的通用配置;一般用于加载除Web层的Bean(如DAO、Service等),以便于与其他任何Web框架集成。

contextConfigLocation:表示用于加载Bean的配置文件;

contextClass:表示用于加载Bean的ApplicationContext实现类,默认WebApplicationContext。

 

 

创建完毕后会将该上下文放在ServletContext:

servletContext.setAttribute(

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,

this.context);

 

 

ContextLoaderListener初始化的上下文和DispatcherServlet初始化的上下文关系,如图3-1

图3-1

从图中可以看出:

ContextLoaderListener初始化的上下文加载的Bean是对于整个应用程序共享的,不管是使用什么表现层技术,一般如DAO层、Service层Bean;

DispatcherServlet初始化的上下文加载的Bean是只对Spring Web MVC有效的Bean,如Controller、HandlerMapping、HandlerAdapter等等,该初始化上下文应该只加载Web相关组件。

3.4、DispatcherServlet初始化顺序

继承体系结构如下所示:

 

1、HttpServletBean继承HttpServlet,因此在Web容器启动时将调用它的init方法,该初始化方法的主要作用

:::将Servlet初始化参数(init-param)设置到该组件上(如contextAttribute、contextClass、namespace、contextConfigLocation),通过BeanWrapper简化设值过程,方便后续使用;

:::提供给子类初始化扩展点,initServletBean(),该方法由FrameworkServlet覆盖。

 

public abstract class HttpServletBean extends HttpServlet implements EnvironmentAware{
@Override
    public final void init() throws ServletException {
       //省略部分代码
       //1、如下代码的作用是将Servlet初始化参数设置到该组件上
//如contextAttribute、contextClass、namespace、contextConfigLocation;
       try {
           PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
           BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
           ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
           bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
           initBeanWrapper(bw);
           bw.setPropertyValues(pvs, true);
       }
       catch (BeansException ex) {
           //…………省略其他代码
       }
       //2、提供给子类初始化的扩展点,该方法由FrameworkServlet覆盖
       initServletBean();
       if (logger.isDebugEnabled()) {
           logger.debug("Servlet '" + getServletName() + "' configured successfully");
       }
    }
    //…………省略其他代码
}

 

 

2、FrameworkServlet继承HttpServletBean,通过initServletBean()进行Web上下文初始化,该方法主要覆盖一下两件事情:

    初始化web上下文;

    提供给子类初始化扩展点;

public abstract class FrameworkServlet extends HttpServletBean {
@Override
    protected final void initServletBean() throws ServletException {
        //省略部分代码
       try {
             //1、初始化Web上下文
           this.webApplicationContext = initWebApplicationContext();
             //2、提供给子类初始化的扩展点
           initFrameworkServlet();
       }
        //省略部分代码
    }
}

 

protected WebApplicationContext initWebApplicationContext() {
        //ROOT上下文(ContextLoaderListener加载的)
       WebApplicationContext rootContext =
              WebApplicationContextUtils.getWebApplicationContext(getServletContext());
       WebApplicationContext wac = null;
       if (this.webApplicationContext != null) {
           // 1、在创建该Servlet注入的上下文
           wac = this.webApplicationContext;
           if (wac instanceof ConfigurableWebApplicationContext) {
              ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
              if (!cwac.isActive()) {
                  if (cwac.getParent() == null) {
                      cwac.setParent(rootContext);
                  }
                  configureAndRefreshWebApplicationContext(cwac);
              }
           }
       }
       if (wac == null) {
             //2、查找已经绑定的上下文
           wac = findWebApplicationContext();
       }
       if (wac == null) {
            //3、如果没有找到相应的上下文,并指定父亲为ContextLoaderListener
           wac = createWebApplicationContext(rootContext);
       }
       if (!this.refreshEventReceived) {
             //4、刷新上下文(执行一些初始化)
           onRefresh(wac);
       }
       if (this.publishContext) {
           // Publish the context as a servlet context attribute.
           String attrName = getServletContextAttributeName();
           getServletContext().setAttribute(attrName, wac);
           //省略部分代码
       }
       return wac;
    }

 

从initWebApplicationContext()方法可以看出,基本上如果ContextLoaderListener加载了上下文将作为根上下文(DispatcherServlet的父容器)。

 

最后调用了onRefresh()方法执行容器的一些初始化,这个方法由子类实现,来进行扩展。

 

 

3、DispatcherServlet继承FrameworkServlet,并实现了onRefresh()方法提供一些前端控制器相关的配置:

 

public class DispatcherServlet extends FrameworkServlet {
     //实现子类的onRefresh()方法,该方法委托为initStrategies()方法。
    @Override
    protected void onRefresh(ApplicationContext context) {
       initStrategies(context);
    }
 
    //初始化默认的Spring Web MVC框架使用的策略(如HandlerMapping)
    protected void initStrategies(ApplicationContext context) {
       initMultipartResolver(context);
       initLocaleResolver(context);
       initThemeResolver(context);
       initHandlerMappings(context);
       initHandlerAdapters(context);
       initHandlerExceptionResolvers(context);
       initRequestToViewNameTranslator(context);
       initViewResolvers(context);
       initFlashMapManager(context);
    }
}

 

从如上代码可以看出,DispatcherServlet启动时会进行我们需要的Web层Bean的配置,如HandlerMapping、HandlerAdapter等,而且如果我们没有配置,还会给我们提供默认的配置。

 

从如上代码我们可以看出,整个DispatcherServlet初始化的过程和做了些什么事情,具体主要做了如下两件事情:

1、初始化Spring Web MVC使用的Web上下文,并且可能指定父容器为(ContextLoaderListener加载了根上下文);

2、初始化DispatcherServlet使用的策略,如HandlerMapping、HandlerAdapter等。

 

 

服务器启动时的日志分析(此处加上了ContextLoaderListener从而启动ROOT上下文容器):

 

 信息: Initializing Spring root WebApplicationContext //由ContextLoaderListener启动ROOT上下文

 

2012-03-12 13:33:55 [main] INFO  org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started

2012-03-12 13:33:55 [main] INFO  org.springframework.web.context.support.XmlWebApplicationContext - Refreshing Root WebApplicationContext: startup date [Mon Mar 12 13:33:55 CST 2012]; root of context hierarchy

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader - Loading bean definitions

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 0 bean definitions from location pattern [/WEB-INF/ContextLoaderListener.xml]

2012-03-12 13:33:55 [main] DEBUG org.springframework.web.context.support.XmlWebApplicationContext - Bean factory for Root WebApplicationContext: org.springframework.beans.factory.support.DefaultListableBeanFactory@1c05ffd: defining beans []; root of factory hierarchy

2012-03-12 13:33:55 [main] DEBUG org.springframework.web.context.support.XmlWebApplicationContext - Bean factory for Root WebApplicationContext:

2012-03-12 13:33:55 [main] DEBUG org.springframework.web.context.ContextLoader - Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT] //将ROOT上下文绑定到ServletContext

2012-03-12 13:33:55 [main] INFO  org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 438 ms  //到此ROOT上下文启动完毕

 

 2012-03-12 13:33:55 [main] DEBUG org.springframework.web.servlet.DispatcherServlet - Initializing servlet 'chapter2'

信息: Initializing Spring FrameworkServlet 'chapter2'  //开始初始化FrameworkServlet对应的Web上下文

2012-03-12 13:33:55 [main] INFO  org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'chapter2': initialization started

2012-03-12 13:33:55 [main] DEBUG org.springframework.web.servlet.DispatcherServlet - Servlet with name 'chapter2' will try to create custom WebApplicationContext context of class 'org.springframework.web.context.support.XmlWebApplicationContext', using parent context [Root WebApplicationContext: startup date [Mon Mar 12 13:33:55 CST 2012]; root of context hierarchy]

//此处使用Root WebApplicationContext作为父容器。

2012-03-12 13:33:55 [main] INFO  org.springframework.web.context.support.XmlWebApplicationContext - Refreshing WebApplicationContext for namespace 'chapter2-servlet': startup date [Mon Mar 12 13:33:55 CST 2012]; parent: Root WebApplicationContext

2012-03-12 13:33:55 [main] INFO  org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/chapter2-servlet.xml]

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader - Loading bean definitions

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.BeanDefinitionParserDelegate - Neither XML 'id' nor 'name' specified - using generated bean name[org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#0]  //我们配置的HandlerMapping

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.BeanDefinitionParserDelegate - Neither XML 'id' nor 'name' specified - using generated bean name[org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter#0] //我们配置的HandlerAdapter

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.BeanDefinitionParserDelegate - Neither XML 'id' nor 'name' specified - using generated bean name [org.springframework.web.servlet.view.InternalResourceViewResolver#0] //我们配置的ViewResolver

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.BeanDefinitionParserDelegate - No XML 'id' specified - using '/hello' as bean name and [] as aliases 

//我们的处理器(HelloWorldController)

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 4 bean definitions from location pattern [/WEB-INF/chapter2-servlet.xml]

2012-03-12 13:33:55 [main] DEBUG org.springframework.web.context.support.XmlWebApplicationContext - Bean factory for WebApplicationContext for namespace 'chapter2-servlet': org.springframework.beans.factory.support.DefaultListableBeanFactory@1372656: defining beans [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#0,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0,/hello]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@1c05ffd

//到此容器注册的Bean初始化完毕

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.web.servlet.DispatcherServlet - Unable to locate MultipartResolver with name 'multipartResolver': no multipart request handling provided

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver'

//默认的LocaleResolver注册

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.web.servlet.theme.FixedThemeResolver'

//默认的ThemeResolver注册

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#0'

//发现我们定义的HandlerMapping 不再使用默认的HandlerMapping。

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter#0'

//发现我们定义的HandlerAdapter 不再使用默认的HandlerAdapter。

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver'

//异常处理解析器ExceptionResolver

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver'

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.web.servlet.DispatcherServlet - Published WebApplicationContext of servlet 'chapter2' as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.chapter2]

//绑定FrameworkServlet初始化的Web上下文到ServletContext

2012-03-12 13:33:56 [main] INFO  org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'chapter2': initialization completed in  297 ms

2012-03-12 13:33:56 [main] DEBUG org.springframework.web.servlet.DispatcherServlet - Servlet 'chapter2' configured successfully

//到此完整流程结束 

 

 

 

从如上日志我们也可以看出,DispatcherServlet会进行一些默认的配置。接下来我们看一下默认配置吧。

 

 

 

3.5、DispatcherServlet默认配置

DispatcherServlet的默认配置在DispatcherServlet.properties(和DispatcherServlet类在一个包下)中,而且是当Spring配置文件中没有指定配置时使用的默认策略:

 

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.annotation.DefaultAnnotationHandlerMapping

 

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\

    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\

    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

 

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\

    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在启动时会自动注册这些特殊的Bean,无需我们注册,如果我们注册了,默认的将不会注册。

 

因此如第二章的BeanNameUrlHandlerMapping、SimpleControllerHandlerAdapter是不需要注册的,DispatcherServlet默认会注册这两个Bean。

 

从DispatcherServlet.properties可以看出有许多特殊的Bean,那接下来我们就看看Spring Web MVC主要有哪些特殊的Bean。

3.6、DispatcherServlet中使用的特殊的Bean

DispatcherServlet默认使用WebApplicationContext作为上下文,因此我们来看一下该上下文中有哪些特殊的Bean:

1、Controller:处理器/页面控制器,做的是MVC中的C的事情,但控制逻辑转移到前端控制器了,用于对请求进行处理;

2、HandlerMapping:请求到处理器的映射,如果映射成功返回一个HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象;如BeanNameUrlHandlerMapping将URL与Bean名字映射,映射成功的Bean就是此处的处理器;

3、HandlerAdapter:HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;如SimpleControllerHandlerAdapter将对实现了Controller接口的Bean进行适配,并且掉处理器的handleRequest方法进行功能处理;

4、ViewResolver:ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为jsp视图;

5、LocalResover:本地化解析,因为Spring支持国际化,因此LocalResover解析客户端的Locale信息从而方便进行国际化;

6、ThemeResovler:主题解析,通过它来实现一个页面多套风格,即常见的类似于软件皮肤效果;

7、MultipartResolver:文件上传解析,用于支持文件上传;

8、HandlerExceptionResolver:处理器异常解析,可以将异常映射到相应的统一错误界面,从而显示用户友好的界面(而不是给用户看到具体的错误信息);

9、RequestToViewNameTranslator:当处理器没有返回逻辑视图名等相关信息时,自动将请求URL映射为逻辑视图名;

10、FlashMapManager:用于管理FlashMap的策略接口,FlashMap用于存储一个请求的输出,当进入另一个请求时作为该请求的输入,通常用于重定向场景,后边会细述。

 

 

到此DispatcherServlet我们已经了解了,接下来我们就需要把上边提到的特殊Bean挨个击破,那首先从控制器开始吧。


微信公众号【Java技术江湖】一位阿里 Java 工程师的技术小站。(关注公众号后回复”Java“即可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源)

cedde63ad8a94ece7aac37190ffe807d1cefc1c2

网友评论

登录后评论
0/500
评论
黄小斜
+ 关注