Spring MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助简化开发,Spring Web MVC也是要简化日常Web开发的。
Spring MVC主要是通过前端控制器controller中的注解来完成请求处理的。前端无论是以何种方式请求,都会通过controller进行轻度处理、转发以及调度后端的处理器进行处理,最后返回正确的视图及响应。以此来看,springMVC既可以返回合适的页面,也可以响应RESTful请求。
Spring MVC简介
Spring MVC的特征
让我们能非常简单的设计出干净和整洁的Web层;
进行更简洁的Web层的开发;
天生与Spring框架集成(如IoC容器、AOP等);
提供强大的约定大于配置的契约式编程支持;
能简单的进行Web层的单元测试;
支持灵活的URL到页面控制器的映射;
非常容易与其他视图技术集成,如Velocity、FreeMarker等等,因为模型数据不放在特定的API里,而是放在一个Model里(Map数据结构实现,因此很容易被其他框架使用);
非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API;
提供一套强大的JSP标签库,简化JSP开发;
支持灵活的本地化、主题等解析;
更加简单的异常处理;
对静态资源的支持;
支持Restful风格。
Spring MVC的优势
清晰的角色划分:前端控制器(DispatcherServlet)、请求到处理器映射(HandlerMapping)、处理器适配器(HandlerAdapter)、视图解析器(ViewResolver)、处理器或页面控制器(Controller)、验证器(Validator)、命令对象(Command 请求参数绑定到的对象就叫命令对象)、表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要;
由于命令对象就是一个POJO,无需继承框架特定API,可以使用命令对象直接作为业务对象;
和Spring 其他框架无缝集成,是其它Web框架所不具备的;
可适配,通过HandlerAdapter可以支持任意的类作为处理器;
可定制性,HandlerMapping、ViewResolver等能够非常简单的定制;
功能强大的数据验证、格式化、绑定机制;
利用Spring提供的Mock对象能够非常简单的进行Web层单元测试;
本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
强大的JSP标签库,使JSP编写更容易。
SpringMVC实现HelloWorld
导入SpringMVC依赖
配置web.xml注册DispatcherServlet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
|
编写SpringMVC的配置文件:springmvc-servlet.xml,添加处理映射器,处理器适配器,视图解析器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean id="/hello" class="com.kuang.controller.HelloController"/> </beans>
|
编写我们要操作业务Controller ,要么实现Controller接口,要么增加注解;需要返回一个ModelAndView,装数据,封视图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package com.heavytiger.controller;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
public class HelloController implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mv = new ModelAndView(); mv.addObject("msg","HelloSpringMVC!"); mv.setViewName("hello"); return mv; } }
|
编写要跳转的jsp页面,显示ModelandView存放的数据,以及正常页面:
1 2 3 4 5 6 7 8 9
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Hello world!</title> </head> <body> ${msg} </body> </html>
|
可能遇到的问题:访问出现404,排查步骤:
- 查看控制台输出,看一下是不是缺少了什么jar包。
- 如果jar包存在,显示无法输出,就在IDEA的项目发布中,添加lib依赖!
- 重启Tomcat 即可解决!
SpringMVC的执行原理
(1) Http请求:客户端请求提交到DispatcherServlet。
(2) 寻找处理器:由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller。
(3) 调用处理器:DispatcherServlet将请求提交到Controller。
(4)(5)调用业务处理和返回结果:Controller调用业务逻辑处理后,返回ModelAndView。
(6)(7)处理视图映射并返回模型: DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图。
(8) Http响应:视图负责将结果显示到客户端。
使用注解开发,实现HelloWorld
导入SpringMVC依赖
配置web.xml注册DispatcherServlet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
|
编写SpringMVC的配置文件:springmvc-servlet.xml,使其支持注解开发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.heavytiger.controller"/> <mvc:default-servlet-handler/>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
</beans>
|
使用注解编写HelloController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.heavytiger.controller;
import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/hello") @Controller public class HelloController {
@RequestMapping("/springMVC") public String helloSpringMVC(Model model) { model.addAttribute("msg", "Hello world, this is SpringMVC!"); return "hello"; }
@RequestMapping("/spring") public String helloSpring(Model model) { model.addAttribute("msg", "Hello world, this is Spring!"); return "hello"; }
@RequestMapping("/mybatis") public String helloMyBatis(Model model) { model.addAttribute("msg", "Hello world, this is MyBatis!"); return "hello"; } }
|
结果:
此时访问不同的网页,导入到了不同的页面中
RESTful API风格
REST:英文representational state transfer直译为表现层状态转移,或者表述性状态转移;Rest是web服务的一种架构风格,一种设计风格,是一种思想;同时Rest不是针对某一种编程语言的。
协议
API与用户的通信协议,总是使用HTTPs协议。
域名
应该尽量将API部署在专用域名之下。
应该尽量将API部署在专用域名之下。
https://api.example.com
如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。
https://example.org/api/
版本(Versioning)
应该将API的版本号放入URL。
https://api.example.com/v1/
另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。
路径(Endpoint)
路径又称”终点”(endpoint),表示API的具体网址。
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
HTTP动词
对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
- DELETE(DELETE):从服务器删除资源。
还有两个不常用的HTTP动词。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
下面是一些例子。
- GET /zoos:列出所有动物园
- POST /zoos:新建一个动物园
- GET /zoos/ID:获取某个指定动物园的信息
- PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
- PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
- DELETE /zoos/ID:删除某个动物园
- GET /zoos/ID/animals:列出某个指定动物园的所有动物
- DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。
- ?limit=10:指定返回记录的数量
- ?offset=10:指定返回记录的开始位置。
- ?page=2&per_page=100:指定第几页,以及每页的记录数。
- ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
- ?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。
- 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
- 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT - [DELETE]:用户删除数据成功。
- 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
- 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
- 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
- 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
错误处理(Error handling)
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
1 2 3
| { error: "Invalid API key" }
|
返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
- GET /collection:返回资源对象的列表(数组)
- GET /collection/resource:返回单个资源对象
- POST /collection:返回新生成的资源对象
- PUT /collection/resource:返回完整的资源对象
- PATCH /collection/resource:返回完整的资源对象
- DELETE /collection/resource:返回一个空文档
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。
1 2 3 4 5 6
| {"link": { "rel": "collection https://www.example.com/zoos", "href": "https://api.example.com/zoos", "title": "List of zoos", "type": "application/vnd.yourformat+json" }}
|
上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。
1 2 3 4 5
| { "current_user_url": "https://api.github.com/user", "authorizations_url": "https://api.github.com/authorizations", }
|
从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。
1 2 3 4 5 6 7
| { "current_user_url": "https://api.github.com/user", "authorizations_url": "https://api.github.com/authorizations", "emails_url": "https://api.github.com/user/emails", "emojis_url": "https://api.github.com/emojis", }
|
上面代码表示,服务器给出了提示信息,以及文档的网址。
其他
(1)API的身份认证应该使用OAuth 2.0框架。
(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。
重定向及转发
通过SpringMVC来实现转发和重定向 - 无需视图解析器;
测试前,需要将视图解析器注释掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Controller public class ResultSpringMVC { @RequestMapping("/rsm/t1") public String test1(){ return "/WEB-INF/jsp/index.jsp"; }
@RequestMapping("/rsm/t2") public String test2(){ return "forward:/WEB-INF/jsp/index.jsp"; }
@RequestMapping("/rsm/t3") public String test3(){ return "redirect:/WEB-INF/jsp/index.jsp"; } }
|
通过SpringMVC来实现转发和重定向 - 有视图解析器;
重定向 , 不需要视图解析器 , 本质就是重新请求一个新地方嘛 , 所以注意路径问题.
可以重定向到另外一个请求实现 .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Controller public class ResultSpringMVC2 { @RequestMapping("/rsm2/t1") public String test1(){ return "test"; }
@RequestMapping("/rsm2/t2") public String test2(){ return "redirect:/index"; }
}
|
数据处理
处理提交的数据
提交的域名称和处理方法的参数名一致
提交数据 : http://localhost:8080/hello?name=helloworld
处理方法 :
1 2 3 4 5
| @RequestMapping("/hello") public String hello(String name){ System.out.println(name); return "hello"; }
|
后台输出 : helloworld
提交的域名称和处理方法的参数名不一致
提交数据 : http://localhost:8080/hello?username=helloworld
处理方法 :
1 2 3 4 5 6
| //@RequestParam("username") : username提交的域的名称 . @RequestMapping("/hello") public String hello(@RequestParam("username") String name){ System.out.println(name); return "hello"; }
|
后台输出 : helloworld
提交的是一个对象
要求提交的表单域和对象的属性名一致,参数使用对象即可
1、实体类
1 2 3 4 5 6 7 8
| public class User { private int id; private String name; private int age; //构造 //get/set //tostring() }
|
2、提交数据 : http://localhost:8080/user?name=helloworld&id=1&age=18
3、处理方法 :
1 2 3 4 5
| @RequestMapping("/user") public String user(User user){ System.out.println(user); return "hello"; }
|
后台输出 : User { id=1, name=’helloworld’, age=18 }
说明:如果使用对象的话,前端传递的参数名和对象名必须一致,否则得到的值就是null,因为无法反射注入。
返回JSON数据
解决乱码问题
增加Spring提供的filter,将请求的编码全部修改为utf-8
1 2 3 4 5 6 7 8 9 10 11 12
| <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/</url-pattern> </filter-mapping>
|
返回json字符串统一解决
在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody !我们在前后端分离开发中,一般都使用 @RestController ,十分便捷!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @RestController public class UserController {
@RequestMapping(value = "/json1") public String json1() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); User user = new User("hello", 3, "男"); String str = mapper.writeValueAsString(user); return str; } }
|
参考资料
[1] 使用Spring MVC - 廖雪峰的官方网站 (liaoxuefeng.com)
[2] 【狂神说Java】SpringMVC最新教程IDEA版通俗易懂_哔哩哔哩_bilibili
[3] Spring MVC【入门】就这一篇! - 简书 (jianshu.com)
[4] RESTful API 设计指南 - 阮一峰的网络日志 (ruanyifeng.com)