Spring MVC
Spring MVC 是目前主流的实现 MVC 设计模式的企业级开发框架,Spring 框架的一个子模块,无需整合,开发起来更加便捷。
什么是 MVC 设计模式?
将应用程序分为 Controller、Model、View 三层,Controller 接收客户端请求,调用 Model 生成业务数据,传递给 View。
Spring MVC 就是对这套流程的封装,屏蔽了很多底层代码,开放出接口,让开发者可以更加轻松、便捷地完成基于 MVC 模式的 Web 开发。
Spring MVC 的核心组件
- DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥。
- Handler:处理器,完成具体的业务逻辑,相当于 Servlet 或 Action。
- HandlerMapping:DispatcherServlet 接收到请求之后,通过 HandlerMapping 将不同的请求映射到不同的 Handler。
- HandlerInterceptor:处理器拦截器,是一个接口,如果需要完成一些拦截处理,可以实现该接口。
- HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)。
- HandlerAdapter:处理器适配器,Handler 执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是由 HandlerApater 来完成,开发者只需将注意力集中业务逻辑的处理上,DispatcherServlet 通过 HandlerAdapter 执行不同的 Handler。
- ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。
- ViewResolver:视图解析器,DispatcheServlet 通过它将逻辑视图解析为物理视图,最终将渲染结果响应给客户端。
Spring MVC 的工作流程
- 客户端请求被 DisptacherServlet 接收。
- 根据 HandlerMapping 映射到 Handler。
- 生成 Handler 和 HandlerInterceptor。
- Handler 和 HandlerInterceptor 以 HandlerExecutionChain 的形式一并返回给 DisptacherServlet。
- DispatcherServlet 通过 HandlerAdapter 调用 Handler 的方法完成业务逻辑处理。
- Handler 返回一个 ModelAndView 给 DispatcherServlet。
- DispatcherServlet 将获取的 ModelAndView 对象传给 ViewResolver 视图解析器,将逻辑视图解析为物理视图 View。
- ViewResovler 返回一个 View 给 DispatcherServlet。
- DispatcherServlet 根据 View 进行视图渲染(将模型数据 Model 填充到视图 View 中)。
- DispatcherServlet 将渲染后的结果响应给客户端。
Spring MVC 流程非常复杂,实际开发中很简单,因为大部分的组件不需要开发者创建、管理,只需要通过配置文件的方式完成配置即可,真正需要开发者进行处理的只有 Handler 、View。
如何使用?
- 创建 Maven 工程,pom.xml
1 | <dependencies> |
- 在 web.xml 中配置 DispatcherServlet。
1 |
|
- springmvc.xml
1 |
|
- 创建 Handler
1 | package com.southwind.controller; |
Spring MVC 注解
- @RequestMapping
Spring MVC 通过 @RequestMapping 注解将 URL 请求与业务方法进行映射,在 Handler 的类定义处以及方法定义处都可以添加 @RequestMapping ,在类定义处添加,相当于客户端多了一层访问路径。
- @Controller
@Controller 在类定义处添加,将该类交个 IoC 容器来管理(结合 springmvc.xml 的自动扫描配置使用),同时使其成为一个控制器,可以接收客户端请求。
1 | package com.southwind.controller; |
- @RequestMapping 相关参数
1、value:指定 URL 请求的实际地址,是 @RequestMapping 的默认值。
1 | "/index") ( |
等于
1 | "/index") (value= |
2、method:指定请求的 method 类型,GET、POST、PUT、DELET。
1 | "/index",method = RequestMethod.GET) (value = |
上述代码表示 index 方法只能接收 GET 请求。
3、params:指定请求中必须包含某些参数,否则无法调用该方法。
1 | "/index",method = RequestMethod.GET,params = {"name","id=10"}) (value = |
上述代码表示请求中必须包含 name 和 id 两个参数,同时 id 的值必须是 10。
关于参数绑定,在形参列表中通过添加 @RequestParam 注解完成 HTTP 请求参数与业务方法形参的映射。
1 | "/index",method = RequestMethod.GET,params = {"name","id=10"}) (value = |
上述代码表示将请求的参数 name 和 id 分别赋给了形参 str 和 age ,同时自动完成了数据类型转换,将 “10” 转为了 int 类型的 10,再赋给 age,这些工作都是由 HandlerAdapter 来完成的。
Spring MVC 也支持 RESTful 风格的 URL。
传统类型:http://localhost:8080/hello/index?name=zhangsan&id=10
REST:http://localhost:8080/hello/index/zhangsan/10
1 | "/rest/{name}/{id}") ( |
通过 @PathVariable 注解完成请求参数与形参的映射。
- 映射 Cookie
Spring MVC 通过映射可以直接在业务方法中获取 Cookie 的值。
1 | "/cookie") ( |
- 使用 JavaBean 绑定参数
Spring MVC 会根据请求参数名和 JavaBean 属性名进行自动匹配,自动为对象填充属性值,同时支持及联属性。
1 | package com.southwind.entity; |
1 | package com.southwind.entity; |
1 | <%-- |
1 | "/save",method = RequestMethod.POST) (value = |
如果出现中文乱码问题,只需在 web.xml 添加 Spring MVC 自带的过滤器即可。
1 | <filter> |
- JSP 页面的转发和重定向:
Spring MVC 默认是以转发的形式响应 JSP。
1、转发
1 | "/forward") ( |
2、重定向
1 | "/redirect") ( |
Spring MVC 数据绑定
数据绑定:在后端的业务方法中直接获取客户端 HTTP 请求中的参数,将请求参数映射到业务方法的形参中,Spring MVC 中数据绑定的工作是由 HandlerAdapter 来完成的。
- 基本数据类型
1 | "/baseType") ( |
@ResponseBody 表示 Spring MVC 会直接将业务方法的返回值响应给客户端,如果不加 @ResponseBody 注解,Spring MVC 会将业务方法的放回值传递给 DispatcherServlet,再由 DisptacherServlet 调用 ViewResolver 对返回值进行解析,映射到一个 JSP 资源。
- 包装类
1 | "/packageType") ( |
包装类可以接收 null,当 HTTP 请求没有参数时,使用包装类定义形参的数据类型,程序不会抛出异常。
@RequestParam
value = “num”:将 HTTP 请求中名为 num 的参数赋给形参 id。
requried:设置 num 是否为必填项,true 表示必填,false 表示非必填,可省略。
defaultValue = “0”:如果 HTTP 请求中没有 num 参数,默认值为0.
- 数组
1 |
|
@RestController 表示该控制器会直接将业务方法的返回值响应给客户端,不进行视图解析。
@Controller 表示该控制器的每一个业务方法的返回值都会交给视图解析器进行解析,如果只需要将数据响应给客户端,而不需要进行视图解析,则需要在对应的业务方法定义处添加 @ResponseBody。
1 |
|
等同于
1 |
|
- List
Spring MVC 不支持 List 类型的直接转换,需要对 List 集合进行包装。
集合封装类
1 | package com.southwind.entity; |
JSP
1 | <%-- |
业务方法
1 | "/list") ( |
处理 @ResponseBody 中文乱码,在 springmvc.xml 中配置消息转换器。
1 | <mvc:annotation-driven> |
- Map
自定义封装类
1 | package com.southwind.entity; |
业务方法
1 | "/map") ( |
JSP
1 | <%-- |
- JSON
客户端发生 JSON 格式的数据,直接通过 Spring MVC 绑定到业务方法的形参中。
处理 Spring MVC 无法加载静态资源,在 web.xml 中添加配置即可。
1 | <servlet-mapping> |
JSP
1 | <%-- |
业务方法
1 | "/json") ( |
Spring MVC 中的 JSON 和 JavaBean 的转换需要借助于 fastjson,pom.xml 引入相关依赖。
1 | <dependency> |
springmvc.xml 添加 fastjson 配置。
1 | <mvc:annotation-driven> |
Spring MVC 模型数据解析
JSP 四大作用域对应的内置对象:pageContext、request、session、application。
模型数据的绑定是由 ViewResolver 来完成的,实际开发中,我们需要先添加模型数据,再交给 ViewResolver 来绑定。
Spring MVC 提供了以下几种方式添加模型数据:
- Map
- Model
- ModelAndView
- @SessionAttribute
- @ModelAttribute
将模式数据绑定到 request 对象。
1、Map
1 | "/map") ( |
JSP
1 | <%-- |
2、Model
1 | "/model") ( |
3、ModelAndView
1 | "/modelAndView") ( |
4、HttpServletRequest
1 | "/request") ( |
5、@ModelAttribute
- 定义一个方法,该方法专门用来返回要填充到模型数据中的对象。
1 |
|
1 |
|
1 |
|
- 业务方法中无需再处理模型数据,只需返回视图即可。
1 | "/modelAttribute") ( |
将模型数据绑定到 session 对象
1、直接使用原生的 Servlet API。
1 | "/session") ( |
2、@SessionAttribute
1 | "user","address"}) (value = { |
对于 ViewHandler 中的所有业务方法,只要向 request 中添加了 key = “user”、key = “address” 的对象时,Spring MVC 会自动将该数据添加到 session 中,保存 key 不变。
1 | .class,Address.class}) (types = {User |
对于 ViewHandler 中的所有业务方法,只要向 request 中添加了数据类型是 User 、Address 的对象时,Spring MVC 会自动将该数据添加到 session 中,保存 key 不变。
将模型数据绑定到 application 对象
1 | "/application") ( |
Spring MVC 自定义数据转换器
数据转换器是指将客户端 HTTP 请求中的参数转换为业务方法中定义的形参,自定义表示开发者可以自主设计转换的方式,HandlerApdter 已经提供了通用的转换,String 转 int,String 转 double,表单数据的封装等,但是在特殊的业务场景下,HandlerAdapter 无法进行转换,就需要开发者自定义转换器。
客户端输入 String 类型的数据 “2019-03-03”,自定义转换器将该数据转为 Date 类型的对象。
- 创建 DateConverter 转换器,实现 Conveter 接口。
1 | package com.southwind.converter; |
- springmvc.xml 配置转换器。
1 | <!-- 配置自定义转换器 --> |
- JSP
1 | <%-- |
- Handler
1 | package com.southwind.controller; |
String 转 Student
StudentConverter
1 | package com.southwind.converter; |
springmvc.xml
1 | <!-- 配置自定义转换器 --> |
JSP
1 | <%-- |
Handler
1 | "/student") ( |
Spring MVC REST
REST:Representational State Transfer,资源表现层状态转换,是目前比较主流的一种互联网软件架构,它结构清晰、标准规范、易于理解、便于扩展。
- 资源(Resource)
网络上的一个实体,或者说网络中存在的一个具体信息,一段文本、一张图片、一首歌曲、一段视频等等,总之就是一个具体的存在。可以用一个 URI(统一资源定位符)指向它,每个资源都有对应的一个特定的 URI,要获取该资源时,只需要访问对应的 URI 即可。
- 表现层(Representation)
资源具体呈现出来的形式,比如文本可以用 txt 格式表示,也可以用 HTML、XML、JSON等格式来表示。
- 状态转换(State Transfer)
客户端如果希望操作服务器中的某个资源,就需要通过某种方式让服务端发生状态转换,而这种转换是建立在表现层之上的,所有叫做”表现层状态转换”。
特点
- URL 更加简洁。
- 有利于不同系统之间的资源共享,只需要遵守一定的规范,不需要进行其他配置即可实现资源共享。
如何使用
REST 具体操作就是 HTTP 协议中四个表示操作方式的动词分别对应 CRUD 基本操作。
GET 用来表示获取资源。
POST 用来表示新建资源。
PUT 用来表示修改资源。
DELETE 用来表示删除资源。
Handler
1 | package com.southwind.controller; |
StudentRepository
1 | package com.southwind.repository; |
StudentRepositoryImpl
1 | package com.southwind.repository.impl; |
Spring MVC 文件上传下载
单文件上传
底层是使用 Apache fileupload 组件完成上传,Spring MVC 对这种方式进行了封装。
- pom.xml
1 | <dependency> |
- JSP
1 | <%-- |
1、input 的 type 设置为 file。
2、form 的 method 设置为 post(get 请求只能将文件名传给服务器)
3、from 的 enctype 设置为 multipart-form-data(如果不设置只能将文件名传给服务器)
- Handler
1 | package com.southwind.controller; |
- springmvc.xml
1 | <!-- 配置上传组件 --> |
- web.xml 添加如下配置,否则客户端无法访问 png
1 | <servlet-mapping> |
多文件上传
pom.xml
1 | <dependency> |
JSP
1 | <%-- |
Handler
1 | "/uploads") ( |
下载
- JSP
1 | <%-- |
- Handler
1 | "/download/{name}") ( |
Spring MVC 表单标签库
- Handler
1 | "/get") ( |
- JSP
1 | <%-- |
1、JSP 页面导入 Spring MVC 表单标签库,与导入 JSTL 标签库的语法非常相似,前缀 prefix 可以自定义,通常定义为 from。
1 | <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> |
2、将 form 表单与模型数据进行绑定,通过 modelAttribute 属性完成绑定,将 modelAttribute 的值设置为模型数据对应的 key 值。
1 | Handeler:modelAndView.addObject("student",student); |
3、form 表单完成绑定之后,将模型数据的值取出绑定到不同的标签中,通过设置标签的 path 属性完成,将 path 属性的值设置为模型数据对应的属性名即可。
1 | 学生ID:<form:input path="id"/><br/> |
常用的表单标签
- from
1 | <form:from modelAttribute="student"/> |
渲染的是 HTML 中的<form></from>
,通过 modelAttribute 属性绑定具体的模型数据。
- input
1 | <form:input path="name"/> |
渲染的是 HTML 中的 <input type="text"/>
,from 标签绑定的是模型数据,input 标签绑定的是模型数据中的属性值,通过 path 属性可以与模型数据中的属性名对应,并且支持及联操作。
1 | <from:input path="address.name"/> |
- password
1 | <form:password path="password"/> |
渲染的是 HTML 中的 <input type="password"/>
,通过 path 属性与模型数据的属性值进行绑定,password 标签的值不会在页面显示。
- checkbox
1 | <form:checkbox path="hobby" value="读书"/> |
1 | student.setFlag(false); |
1 | checkbox:<form:checkbox path="flag" value="flag"></form:checkbox><br/> |
渲染的是 HTML 中的 <input type="checkbox"/>
,通过 path 与模型数据的属性值进行绑定,可以绑定 boolean、数组和集合。
如果绑定 boolean 值,若该变量的值为 true,则表示该复选框选中,否则表示不选中。
如果绑定数组或者集合,数组/集合中的元素等于 checkbox 的 value 值,则选中。
1 | student.setHobby(Arrays.asList("读书","看电影","玩游戏")); |
1 | 爱好:<form:checkbox path="hobby" value="摄影"></form:checkbox>摄影<br/> |
- checkboxes
1 | <form:checkboxes items=${student.hobby} path="selecHobby"/> |
渲染的是 HTML 中的一组 <input type="checkbox"/>
,是对 <form:checkbox/>
的一种简化,需要结合 items 和 path 属性来使用,items 绑定被遍历的集合或数组,path 绑定被选中的集合或数组,可以这样理解,items 为全部可选集合,path 为默认的选中集合。
1 | student.setHobby(Arrays.asList("摄影","读书","听音乐","看电影","旅游","玩游戏")); |
1 | 爱好:<form:checkboxes path="selectHobby" items="${student.hobby}"/><br/> |
需要注意的是 path 可以直接绑定模型数据的属性值,items 则需要通过 EL 表达式的形式从域对象中获取数据,不能直接写属性名。
- rabiobutton
1 | <from:radiobutton path="radioId" value="0"/> |
渲染的是 HTML 中的一个 <input type="radio"/>
,绑定的数据与标签的 value 值相等则为选中,否则不选中。
1 | student.setRadioId(1); |
1 | radiobutton:<form:radiobutton path="radioId" value="1"/>radiobutton<br/> |
- radiobuttons
1 | <form:radiobuttons itmes="${student.grade}" path="selectGrade"/> |
渲染的是 HTML 中的一组 <input type="radio"/>
,这里需要结合 items 和 path 两个属性来使用,items 绑定被遍历的集合或数组,path 绑定被选中的值,items 为全部的可选类型,path 为默认选中的选项,用法与 <form:checkboxes/>
一致。
1 | Map<Integer,String> gradeMap = new HashMap<>(); |
1 | 学生年级:<form:radiobuttons items="${student.gradeMap}" path="selectGrade"/><br/> |
- select
1 | <form:select items="${student.citys}" path="selectCity"/> |
渲染的是 HTML 中的一个 <select/>
标签,需要结合 items 和 path 两个属性来使用,items 绑定被遍历的集合或数组,path 绑定被选中的值,用法与 <from:radiobuttons/>
一致。
1 | Map<Integer,String> cityMap = new HashMap<>(); |
1 | 所在城市:<form:select items="${student.cityMap}" path="selectCity"></form:select><br/> |
- options
form:select
结合 form:options
的使用,from:select
只定义 path 属性,在 form:select
标签内部添加一个子标签 form:options
,设置 items 属性,获取被遍历的集合。
1 | 所在城市:<form:select path="selectCity"> |
option
form:select
结合form:option
的使用,from:select
定义 path 属性,给每一个form:option
设置 value 值,path 的值与哪个 value 值相等,该项默认选中。
1 | 所在城市:<form:select path="selectCity"> |
- textarea
渲染的是 HTML 中的一个 <textarea/>
,path 绑定模型数据的属性值,作为文本输入域的默认值。
1 | student.setIntroduce("你好,我是..."); |
1 | 信息:<form:textarea path="introduce"/><br/> |
- errors
处理错误信息,一般用在数据校验,该标签需要结合 Spring MVC 的验证器结合起来使用。
Spring MVC 数据校验
Spring MVC 提供了两种数据校验的方式:1、基于 Validator 接口。2、使用 Annotation JSR - 303 标准进行校验。
基于 Validator 接口的方式需要自定义 Validator 验证器,每一条数据的验证规则需要开发者手动完成,使用 Annotation JSR - 303 标准则不需要自定义验证器,通过注解的方式可以直接在实体类中添加每个属性的验证规则,这种方式更加方便,实际开发中推荐使用。
基于 Validator 接口
- 实体类 Account
1 | package com.southwind.entity; |
- 自定义验证器 AccountValidator,实现 Validator 接口。
1 | package com.southwind.validator; |
- 控制器
1 | package com.southwind.controller; |
- springmvc.xml 配置验证器。
1 | <bean id="accountValidator" class="com.southwind.validator.AccountValidator"></bean> |
- JSP
1 | <%-- |
Annotation JSR - 303 标准
使用 Annotation JSR - 303 标准进行验证,需要导入支持这种标准的依赖 jar 文件,这里我们使用 Hibernate Validator。
- pom.xml
1 | <!-- JSR-303 --> |
- 通过注解的方式直接在实体类中添加相关的验证规则。
1 | package com.southwind.entity; |
- ValidatorHandler
1 | "/register") ( |
- springmvc.xml
1 | <mvc:annotation-driven /> |
- JSP
1 | <%-- |
校验规则详解:
@Null 被注解的元素必须为null
@NotNull 被注解的元素不能为null
@Min(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注解的元素必须是一个数字,其值必须小于于等于指定的最大值
@Email 被注解的元素必须是电子邮箱地址
@Pattern 被注解的元素必须符合对应的正则表达式
@Length 被注解的元素的大小必须在指定的范围内
@NotEmpty 被注解的字符串的值必须非空
Null 和 Empty 是不同的结果,String str = null,str 是 null,String str = “”,str 不是 null,其值为空。