终于学习完了Spring的全部内容,那么接下来的精力就集中在了Spring MVC上边,什么是Spring MVC呢,其实Spring MVC就是通过Java实现MVC的轻量级Web框架,既然是Spring MVC,也就是深度兼容到Spring中的。可以这么理解,其实MVC是一种架构思想,实现方式有很多种,之前我们在Java Web基础系列里【Java Web编程 十四】深入理解MVC架构模式通过JSP+Servlet+JDBC组合实现了MVC架构,抑或是我们古早的Struts框架实现MVC架构,其实就是思想的一种实现方式。那么从今天开始我们就使用Spring MVC来代替JSP+Servlet+JDBC组合实现下MVC架构。
回顾MVC思想
MVC 全名是 Model View Controller,一种软件设计典范,用一种业务数据、逻辑、界面显示分离的方法组织代码,各部分职责如下:
- 视图
视图是用户看到并与之交互的界面。对老式的Web应用程序来说,视图就是由HTML元素组成的界面,在新式的Web应用程序中,HTML依旧在视图中扮演着重要的角色,但是已经被一些能显示动态数据的JSP逐步取代了,MVC好处是它能为应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,不管这些数据来源是什么,作为视图来讲,它只是作为一种输出数据并允许用户操纵的方式。 - 模型
模型表示企业数据和业务规则。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,就是说模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。不能将模型看出一个只有数据的类,其实模型也包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),现在一般会更加细分:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务两部分 - 控制器
控制器接受用户的输入并调用模型和视图去完成用户的需求,所以当单击Web页面中的超链接和发送HTML表单时,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
在前后端不分离的站点中,JSP充当了视图,Servlet充当了控制器,Model充当了模型。抽象意义上的职责如下图所示:
现在的MVC模型已经演化为如下操作过程,下面的整个项目就是依据这样的分层构建的
MVC各部分的简要职责以及各个模块活动的数据Model如下:
- View:视图:显示页面 (返回:DTO转VO)
- Controller:控制器:取得表单数据;调用业务逻辑;转向指定的页面(入参:DTO转PO,返回:PO转DTO)
- Model:模型:定义业务数据模型;处理业务逻辑;持久化数据状态
另外基于传递的数据Model,我们可以把其中涉及的数据模型进行划分:PO,持久对象,对应数据库表的对象模型;DTO,传输对象,前端发给后端的请求对象;VO,视图对象,后端返回给前端的对象。
回顾JSP+Servlet+JDBC组合实现方式
整体的项目结构如下,前端页面展示使用了JSP,数据持久化使用了JDBC,Controller使用了Servlet,当然因为项目整体比较简单所以就使用了一个Model贯穿始终,实际上应该按照使用层的不同分别定义的:PO,DTO,VO。
可以看到一个MVC框架的工作主要是这几步:
- 将url映射到Java类或Java类的方法
- 封装用户提交的数据 (DTO)
- 处理请求–>调用相关的业务处理(如需持久化数据封装PO)–>封装响应数据(VO)
- 将响应的数据进行渲染
. jsp / html
等表示层数据
了解了一个MVC框架的具体工作流程,我们再来看看Spring MVC是如何进行工作的。
Spring MVC基本概念
Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架,目前的版本已经迭代到了官方文档-5.2.0.RELEASE
为什么要使用Spring MVC
我们新来看下为什么使用MVC框架,然后看下历史的框架对比Struts和Spring MVC,最后看下为什么我们更多使用Spring MVC
为什么使用MVC框架
为什么使用MVC框架呢, web容器创造了servlet接口,servlet接口就是开发人员自己实现业务逻辑的地方,servlet接口最大的特点就是根据http协议的特点进行定义:
- 一方面做servlet开发时候如果使用者对http协议特点不是特别熟悉,都会碰到或多或少令人迷惑的问题,特别是碰到一些复杂特殊的请求时候:例如文件上传,返回特殊的文件格式到浏览器,这时候使用servlet开发就不是很方便了,servlet开发还有个问题可能大家常常被忽视,就是请求的数据的类型转化,http协议传输都是文本形式,到了web容器解析后也是文本类型,如果碰到货币,数字,日期这样的类型需要我们根据实际情况进行转化,如果页面传送的信息非常多,我们就不得不做大量类型转化,这种工作没有什么技术含量,是个体力活而且很容易导致程序错误
- 另一方面,通用的权限校验、日志记录等通用的内容,每个servlet都需要重复处理一遍,浪费时间
那么为什么MVC框架可以解决这些问题呢?这个就需要聊到一个概念:前端控制器。什么是前端控制器?Front Controller
模式 要求在WEB应用系统的前端( Front
)设置一个入口控制器( Controller
) ,是用来提供一个集中的请求处理机制,所有的请求都被发往该控制器统统一处理 ,然后把请求分发给各自相应的处理程序。如果没有前端控制器,那么每次请求都只能找到对应的Servlet去处理:
类似代码如下,每个请求都需要精准的找到Servlet的地址进行请求:
package com.example.spring_mvc; import java.io.*; import javax.servlet.http.*; import javax.servlet.annotation.*; @WebServlet(name = "helloServlet", value = "/hello-servlet") public class HelloServlet extends HttpServlet { private String message; public void init() { message = "Hello World!"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.setContentType("text/html"); // Hello PrintWriter out = response.getWriter(); out.println("<html><body>"); out.println("<h1>" + message + "</h1>"); out.println("</body></html>"); } public void destroy() { } }
如果有通用的一些代码:如权限检查,授权,日志记录等,每个Servlet都需要重复处理,当然可以使用过滤器去实现,但是实现复杂度还是比较高,所以我们的框架会给我们封装一个前端控制器来解决这个问题:
我们发现这个前端控制器,很像web里面的Filter过滤器:一般的, 我们把处理请求的对象称之为处理器 (Controller)。
- Apache习惯称之为Action , 如UserAction.
- Spring习惯称之为 Controller , 如UserController.
从这里能看出使用MVC框架必须在web.xml
中配置前端控制器, 一般的要么是要Filter , 要么是Servlet。Struts2基于Filter,SpringMVC基于Servlet
SpringMVC和Struts2对比
SpringMVC和Struts2对比如下,从集成角度和学习成本角度去看,其实Spring MVC更胜一筹
- Struts2是类级别的拦截, 一个类对应一个request上下文,SpringMVC是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上SpringMVC就容易实现restful url,而struts2的架构实现起来要费劲,因为Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了
- 数据共享方面: SpringMVC的方法之间基本上独立的,独享request response数据,请求数据通过参数获取,处理结果通过ModelMap交回给框架,方法之间不共享变量,而Struts2搞的就比较乱,虽然方法之间也是独立的,但其所有Action变量是共享的,这不会影响程序运行,却给我们编码 读程序时带来麻烦,每次来了请求就创建一个Action,一个Action对象对应一个request上下文
- 拦截器实现机制方面,Struts2有以自己的interceptor机制,SpringMVC用AOP方式,这样导致Struts2的配置文件量还是比SpringMVC大
- Ajax的实现方式方面:SpringMVC集成了Ajax,使用非常方便,只需一个注解@ResponseBody就可以实现,然后直接返回响应文本即可,而Struts2拦截器集成了Ajax,在Action中处理时一般必须安装插件或者自己写代码集成进去,使用起来也相对不方便
- 控制器实现方面: Spring MVC的前端控制器是Servlet, 而Struts2是Filter
- 参数验证方面:SpringMVC验证支持JSR303,处理起来相对更加灵活方便,而Struts2验证比较繁琐,感觉太烦乱
- 请求性能方面:Spring MVC会稍微比Struts2 快些,Spring MVC是基于方法设计, 处理器是单例;而Sturts2 是基于类设计,每次发一次请求都会实例一个新的Action对象,Action是多例的。
总之SpringMVC目前的使用率已经远远超过了Struts2, 所以我们现在更多使用Spring MVC做框架的MVC,甚至和Spring结合起来,SpringMVC已经解决零配置了
Spring MVC的优点
Spring的web框架围绕DispatcherServlet [ 调度Servlet ] 设计。DispatcherServlet的作用是将请求分发到不同的处理器,Spring MVC有如下的特点:
- 轻量级,简单易学
- 高效 , 基于请求响应的MVC框架
- 与Spring兼容性好,无缝结合
- 约定优于配置
- 功能强大:RESTful、数据验证、格式化、本地化、主题等
- 简洁灵活
正因为SpringMVC简单 , 便捷 , 易学 , 天生和Spring无缝集成(使用SpringIoC和Aop) , 使用约定优于配置 、能够进行简单的junit测试 、支持Restful风格 、异常处理、本地化、国际化 、 数据验证 、 类型转换等,所以我们更倾向于使用Spring MVC
DispatcherServlet中心控制器
Spring MVC框架像许多其他MVC框架一样, 以请求为驱动 , 围绕一个中心Servlet分派请求及提供其他功能,DispatcherServlet
是一个实际的Servlet (它继承自HttpServlet 基类)。
当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。这样我们的原有的MVC执行方式如下图:
SpringMVC执行原理
简要分析执行流程:DispatcherServlet
表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求。我们假设请求的url为 : http://localhost:8080/spring-mvc/hello
,如上url拆分成三部分:
http://localhost:8080 服务器域名 spring-mvc 部署在服务器上的web站点 hello 表示具体的后端控制器
如上url表示为:请求位于服务器localhost:8080上的spring-mvc站点的hello控制器。
如上图拆解的整体请求流程如下:其中DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。
- 用户发出请求,DispatcherServlet接收请求并拦截请求
- HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler
- HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:hello
- HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等
- HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler
- Handler让具体的Controller执行
- Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView
- HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet
- DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名
- 视图解析器将解析的逻辑视图名传给DispatcherServlet
- DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图
- 最终视图呈现给用户
所以其实Spring MVC的作用就是一个前置的Servlet的集中管理器
总结一下
最初我们使用简单的JSP和Servlet去处理请求,后来发现业务逻辑越来越复杂,耦合度太高,于是我们发明了MVC思想,并使用JSP+Servlet+JDBC组合实现了最初的MVC架构,随着业务的发展我们觉得MVC架构的每个部分都能被框架化,例如持久层就可以使用MyBatis去优化来取代JDBC,Spring接管和取代所有的业务逻辑Model层,甚至兼容整合其它框架,而前置的简单Servlet请求也可以被框架化为Spring MVC去实现。所以重要的其实是MVC思想,从来不是各种各样的框架,框架的都是为了这种思想落地而产生的实际形式,这是框架存在的意义,搞清楚这点,我觉得才有学习的依据。