菜鸟之路——Spring MVC(一)初探

一、MVC框架是什么

         模型Model-视图View-控制器Controller(MVC)是一个众所周知的以设计界面应用程序为基础的设计模式。它主要通过分离模型、视图及控制器在应用程序中的角色将业务逻辑从界面中解耦。通常,模型负责封装应用程序数据在视图层展示。视图仅仅只是展示这些数据,不包含任何业务逻辑。控制器负责接收来自用户的请求,并调用后台服务来处理业务逻辑。处理后,后台业务层可能会返回了一些数据在视图层展示。控制器收集这些数据及准备模型在视图层展示。MVC模式的核心思想是将业务逻辑从界面中分离出来,允许它们单独改变而不会相互影响。

                                                                             

、Spring MVC是什么

        Spring MVC,又名Spring Web MVC,是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,与Struts2框架类似,使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型。其主要目的是构建Web应用程序的全功能MVC模块,在View层和Controller层之间进行映射,完成View层和Model层之间的数据转换。

   Spring MVC框架通过 DispatcherServlet 分发请求处理程序,包括可配置的映射,视图解决方案,语言,时区,主题以及上传文件。默认的处理
器是基于 @Controller 和@RequestMapping 注解处理各种方法。从 Spring 3.0开始,@Controller 机制允许用户通过 @PathVariable 注解及其它特
性创建 RESTful Web站点和应用。

  Spring Web MVC的一个关键原则是对扩展开放,对修改封闭,也即我们常说的“开闭原则”。所以Spring Web MVC核心类中的一些方法被标记为 final 类型的。

  在 Spring Web MVC中,用户可以将任何对象作为命令或者请求访问的对象,而不需要实现特定framework框架的接口或基类。Spring的数据绑定机制是非常灵活的:比如,它能把类型匹配错误当做验证性错误处理而不是系统错误,这样错误类型就成为可评估的错误。尽管它像你不需要重复的业务对象的属性一样简单,表单中的无类型字符串简单处理无效的提交,或将字符串转为合法的类型。但它通常是可以直接绑定的业务对象。

  Spring的解决方案通常是非常灵活的。Controller的职责是选择一个视图并将数据映射到该视图上,当然 Controller 也能够直接写输出流来完成请求。 视图机制是高可配的,通过文件扩展名,Accept header头类型,bean名称,属性文件以及自定义的 ViewResolver实现。MVC中的 Model是 Map 接口,它允许完整的抽象视图技术,可以与JSP,Velocity及Freemarker这样基于模板的渲染技术集成,也可以生成XML,JSON,Atom以及其它类型的内容。Map模型很容易转换成其它的格式,比如JSP请求属性,Velocity模板模型。

   Spring MVC 4特性: 

  1. 角色分离:controller, validator, command object, form object, model object, DispatherServlet, handler mapping, view resolver等等都可以单独实现。

  2. 强大简单的配置框架及应用类作为JavaBeans:配置包括简单的上下文引用,比如web前端控制器对业务对象以及验证器的引用。

  3. 适应性,灵活性,无侵入性:可以使用诸如@RequestParam, @RequestHeader, @Pathvariable这样的注解定义任意控制器的方法签名。

  4. 可重用业务代码:使用存在的业务对象当做command object或 form object,而不是继承框架的基类当做镜像。

  5. 可定制的绑定和验证机制:类型不匹配当做应用级别的验证错误,使用传值,本地化日期和数据绑定等替代将手动将对象转为字符串或转换为业务对象。

  6. 可定制的handler映射和视图机制。Handler映射和视图机制从简单的URL配置到复杂的,专门的解决策略都能处理。

  7. 灵活的模型转移机制:使用Map中key/value这样的模型转移机制很容易与其他视图技术集成。

  8. 支持国际化,支持时区,支持主题,支持JSP,支持JSTL,支持Velocity等。

  9. JSP标签库,又称Spring标签库支持数据绑定和主题。

  关于Spring MVC,开发者使用最多的是其 DispatcherServlet转发,通过注解@Controller配置转发路径,并通过ModelAndView返回视图。

  在Spring MVC应用程序中,模型通常由POJO对象组成,它在业务层中被处理,在持久层中被持久化。视图通常是用JSP标准标签库(JSTL)编写的JSP模板。控制器部分是由dispatcher servlet负责。

                


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

三、Spring MVC 能帮我们做什么

  √让我们能非常简单的设计出干净的Web层和薄薄的Web层;
  √进行更简洁的Web层的开发;
  √天生与Spring框架集成(如IoC容器、AOP等);
  √提供强大的约定大于配置的契约式编程支持;
  √能简单的进行Web层的单元测试;
  √支持灵活的URL到页面控制器的映射;
  √非常容易与其他视图技术集成,如Velocity、FreeMarker等等,因为模型数据不放在特定的API里,而是放在一个Model里(Map数据结构实现,因此很容易被其他框架使用);
  √非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API;
  √提供一套强大的JSP标签库,简化JSP开发;
  √支持灵活的本地化、主题等解析;
  √更加简单的异常处理;
  √对静态资源的支持;
  √支持Restful风格。

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

      

  1、 首先用户发送请求————>前端控制器,前端控制器根据请求信息(如URL)来决定选择哪一个页面控制器进行处理并把请求委托给它,即以前的控制器的控制逻辑部分;图中的1、2步骤;
  2、 页面控制器接收到请求后,进行功能处理,首先需要收集和绑定请求参数到一个对象,这个对象在Spring Web MVC中叫命令对象,并进行验证,然后将命令对象委托给业务对象进行处理;处理完毕后返回一个ModelAndView(模型数据和逻辑视图名);图中的3、4、5步骤;
  3、 前端控制器收回控制权,然后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染;图中的步骤6、7;
  4、 前端控制器再次收回控制权,将响应返回给用户,图中的步骤8;至此整个结束。
  问题:
  1、 请求如何给前端控制器?
  2、 前端控制器如何根据请求信息选择页面控制器进行功能处理?
  3、 如何支持多种页面控制器呢?
  4、 如何页面控制器如何使用业务对象?
  5、 页面控制器如何返回模型数据?
  6、 前端控制器如何根据页面控制器返回的逻辑视图名选择具体的视图进行渲染?
  7、 不同的视图技术如何使用相应的模型数据?
  首先我们知道有如上问题,那这些问题如何解决呢?请让我们先继续,在后边依次回答。

 

  Spring Web MVC核心架构图如下:

  

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

 //前端控制器分派方法
 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		int interceptorIndex = -1;

		try {
			ModelAndView mv;
			boolean errorView = false;
			try {
                                //检查是否是请求是否是multipart(如文件上传),如果是将通过MultipartResolver解析
				processedRequest = checkMultipart(request);
                                //步骤2、请求到处理器(页面控制器)的映射,通过HandlerMapping进行映射
				mappedHandler = getHandler(processedRequest, false);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}
                                 //步骤3、处理器适配,即将我们的处理器包装成相应的适配器(从而支持多种类型的处理器)
				 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                                 // 304 Not Modified缓存支持
			        //此处省略具体代码
				// 执行处理器相关的拦截器的预处理(HandlerInterceptor.preHandle)
				//此处省略具体代码
				// 步骤4、由适配器执行处理器(调用处理器相应功能处理方法)
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				// Do we need view name translation?
				if (mv != null && !mv.hasView()) {
					mv.setViewName(getDefaultViewName(request));
				}

				// 执行处理器相关的拦截器的后处理(HandlerInterceptor.postHandle)
				//此处省略具体代码
			}
			catch (ModelAndViewDefiningException ex) {
				logger.debug("ModelAndViewDefiningException encountered", ex);
				mv = ex.getModelAndView();
			}
			catch (Exception ex) {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(processedRequest, response, handler, ex);
				errorView = (mv != null);
			}

			//步骤5 步骤6、解析视图并进行视图的渲染
                        //步骤5 由ViewResolver解析View(viewResolver.resolveViewName(viewName, locale))
                       //步骤6 视图在渲染时会把Model传入(view.render(mv.getModelInternal(), request, response);)
			if (mv != null && !mv.wasCleared()) {
				render(mv, processedRequest, 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");
				}
			}

			// 执行处理器相关的拦截器的完成后处理(HandlerInterceptor.afterCompletion)
			//此处省略具体代码
		catch (Exception ex) {
			// Trigger after-completion for thrown exception.
			triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
			throw ex;
		}
		catch (Error err) {
			ServletException ex = new NestedServletException("Handler processing failed", err);
			// Trigger after-completion for thrown exception.
			triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
			throw ex;
		}
		finally {
			// Clean up any resources used by a multipart request.
			if (processedRequest != request) {
				cleanupMultipart(processedRequest);
			}
		}
	}
 核心架构的具体流程步骤如下:
  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中进行部署描述。
  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、处理器(页面控制器)的配置,从而进行功能处理
五、Spring 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风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。

展开阅读全文

没有更多推荐了,返回首页