day02 springmvc
第一章 RESTFul风格交互方式
第一节 RESTFul概述
1. REST的概念
REST:Representational State Transfer,表现层资源状态转移。
- 定位:互联网软件架构风格
- 倡导者:Roy Thomas Fielding
- 文献:Roy Thomas Fielding的博士论文
2. REST要解决的问题: 将针对功能设计系统转变成针对资源设计系统
传统的软件系统仅在本地工作,但随着项目规模的扩大和复杂化,不但整个项目会拓展为分布式架构,很多功能也会通过网络访问第三方接口来实现。在通过网络访问一个功能的情况下,我们不能轻易假设网络状况稳定可靠。所以当一个请求发出后没有接收到对方的回应,那我们该如何判定本次操作成功与否?
下面以保存操作为例来说明一下针对功能和针对资源进行操作的区别:
-
针对功能设计系统
保存一个 Employee 对象,没有接收到返回结果,判定操作失败,再保存一次。但是其实在服务器端保存操作已经成功了,只是返回结果在网络传输过程中丢失了。而第二次的补救行为则保存了重复、冗余但 id 不同的数据,这对整个系统数据来说是一种破坏。
-
针对资源设计系统
针对 id 为 3278 的资源执行操作,服务器端会判断指定 id 的资源是否存在。如果不存在,则执行保存操作新建数据;如果存在,则执行更新操作。所以这个操作不论执行几次,对系统的影响都是一样的。在网络状态不可靠的情况下可以多次重试,不会破坏系统数据。
幂等性:如果一个操作执行一次和执行 N 次对系统的影响相同,那么我们就说这个操作满足幂等性。而幂等性正是 REST 规范所倡导的。
3. RESTFul风格的架构特点
3.1 通过URL就知道要操作什么资源
REST是针对资源设计系统,所以在REST中一个URL就对应一个资源, 为实现操作幂等性奠定基础。
3.2 通过Http请求的方式就知道要对资源进行何种操作
在REST中,针对同一资源的增删改查操作的URL是完全相同的,它是通过Http协议的不同请求方式来区分不同操作的
REST 风格主张在项目设计、开发过程中,具体的操作符合 HTTP 协议定义的请求方式的语义。
操作 | 请求方式 |
---|---|
查询操作 | GET |
保存操作 | POST |
删除操作 | DELETE |
更新操作 | PUT |
另有一种说法:
- POST 操作针对功能执行,没有锁定资源 id,是非幂等性操作。
- PUT 操作锁定资源 id,即使操作失败仍然可以针对原 id 重新执行,对整个系统来说满足幂等性。
- id 对应的资源不存在:执行保存操作
- id 对应的资源存在:执行更新操作
3.3 URL更加简洁也更加隐晦
REST风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。还有一点是不要使用请求扩展名。
使用问号键值对的方式给服务器传递数据太明显,容易被人利用来对系统进行破坏。使用 REST 风格携带数据不再需要明显的暴露数据的名称。
操作 | 传统风格 | REST 风格 |
---|---|---|
保存 | /CRUD/saveEmp | URL 地址:/CRUD/emp 请求方式:POST |
删除 | /CRUD/removeEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:DELETE |
更新 | /CRUD/updateEmp | URL 地址:/CRUD/emp 请求方式:PUT |
查询(表单回显) | /CRUD/editEmp?empId=2 | URL 地址:/CRUD/emp/2 请求方式:GET |
第二节 四种请求方式的映射
1. 为什么要进行请求方式的映射
在 HTML 中,GET 和 POST 请求可以天然实现,但是 DELETE 和 PUT 请求无法直接做到。SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求。
2. 具体执行映射操作
2.1 映射PUT 请求
2.1.1 修改web.xml文件
<!--一定要配置在解决乱码的Filter之后-->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.1.2 页面的表单
- 要点1:原请求方式必须是 post
- 要点2:新的请求方式名称通过请求参数发送
- 要点3:请求参数名称必须是 _method
- 要点4:请求参数的值就是要改成的请求方式
<form th:action="@{/rest/movie}" method="post">
<input type="hidden" name="_method" value="put"/>
<button>发送请求</button>
</form>
2.1.3 handler方法
@PutMapping("/movie")
public String updateMovie(){
logger.debug("PUT请求....");
return "target";
}
2.2 映射DELETE请求
2.2.1 web.xml中要维持之前映射PUT请求的配置
2.2.2 前端页面
通常删除超链接会出现在列表页面:
<h3>将XXX请求转换为DELETE请求</h3>
<div id="app">
<table id="dataTable">
<tr>
<th>姓名</th>
<th>年龄</th>
<th>删除</th>
</tr>
<tr>
<td>张三</td>
<td>40</td>
<td>
<a th:href="@{/rest/movie}" @click="deleteMovie">删除</a>
</td>
</tr>
<tr>
<td>李四</td>
<td>30</td>
<td>
<a th:href="@{/rest/movie}" @click="deleteMovie">删除</a>
</td>
</tr>
</table>
</div>
创建负责转换的表单
<form id="myForm" method="post">
<input type="hidden" name="_method" value="delete"/>
</form>
使用vue给删除超链接绑定单击响应函数:
- 引入vue
<!--引入vue-->
<script th:src="@{/static/javaScript/vue.js}"></script>
- 绑定单击响应函数
var vue = new Vue({
"el":"#app",
"methods":{
deleteMovie(){
console.log("aaaaaaa")
//真正发送删除请求:
//1. 阻止标签的默认行为
event.preventDefault()
//2. 创建一个空表单:先动态设置表单的action
var myForm = document.getElementById("myForm");
myForm.action = event.target.href
//并且使用js代码提交表单
myForm.submit()
}
}
});
2.2.3 handler方法
@DeleteMapping("/movie")
public String deleteMovieById(){
logger.debug("DELETE请求....");
return "target";
}
第三节 PathVariable注解获取路径参数
1. REST 风格路径参数
请看下面链接:
/emp/20
/shop/product/iphone
如果我们想要获取链接地址中的某个部分的值,就可以使用 @PathVariable 注解,例如上面地址中的20、iphone部分。
2. 具体操作
2.1 单个路径参数
2.1.1 发送请求携带参数
<a th:href="@{/rest/movie/2}">携带参数movieId</a>
2.1.2 handler方法获取路径参数
//注意:{movieId是一个占位符},表示获取该位置的值,@PathVariable("movieId")表示获取占位符为"movieId"的值
@GetMapping("/movie/{movieId}")
public String findMovieById(@PathVariable("movieId") Integer movieId){
logger.debug("GET请求...."+movieId);
return "target";
}
2.2 多个路径参数
2.2.1 发送请求携带参数
<a th:href="@{/rest/movie/2/22/123}">携带多个参数</a>
2.2.2 handler方法获取路径参数
@GetMapping("/movie/{categoryId}/{groupId}/{movieId}")
public String findMovieById(@PathVariable("categoryId") Integer categoryId,@PathVariable("groupId")Integer groupId,@PathVariable("movieId") Integer movieId){
logger.debug("GET请求...."+categoryId+":"+groupId+":"+movieId);
return "target";
}
第四节 RESTFul综合案例
1.案例准备
将前面的传统CRUD案例复制并且重新导入
2. 功能清单
功能 | URL 地址 | 请求方式 |
---|---|---|
访问首页 | / | GET |
查询全部数据 | /soldier | GET |
删除 | /soldier/2 | DELETE |
跳转到添加数据的表单 | /soldier/add.html | GET |
执行保存 | /soldier | POST |
跳转到更新数据的表单 | /soldier/2 | GET |
执行更新 | /soldier | PUT |
3.访问首页
不需要做修改
4.查询全部
4.1 处理器方法需修改 注解改为@GetMapping
@GetMapping
public String findAll(Model model){
List<Soldier> soldierList = soldierService.findAll();
model.addAttribute("soldierList", soldierList);
return PAGE_LIST;
}
4.2 index页面修改路径
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>欢迎来到首页</h1>
<a th:href="@{/soldier}">展示士兵列表</a>
</body>
</html>
4.3 list页面展示 不需修改
5. 删除数据
重点在于将 GET 请求转换为 DELETE。基本思路是:通过一个通用表单,使用 Vue 代码先把 GET 请求转换为 POST,然后再借助 hiddenHttpMethodFilter 在服务器端把 POST 请求转为 DELETE。
5.2.1 前端代码
<td>
<a th:href="@{/soldier/}+${soldier.soldierId}" onclick="deleteSoldier()">删除 </a>
</td>
<!-- 组件名称:通用表单 -->
<!-- 组件作用:把删除超链接的 GET 请求转换为 POST,并携带 _method 请求参数 -->
<form id="deleteById" method="post">
<!-- 请求参数作用:告诉服务器端 hiddenHttpMethodFilter 要转换的目标请求方式 -->
<!-- 请求参数名:_method,这是 hiddenHttpMethodFilter 中规定的 -->
<!-- 请求参数值:delete,这是因为我们希望服务器端将请求方式最终转换为 delete -->
<input type="hidden" name="_method" value="delete"/>
</form>
<script>
function deleteSoldier() {
// 阻止a标签的默认发送请求
event.preventDefault();
// 获取form标签
var element = document.getElementById("deleteById");
// 设置form标签的action值为a标签的href
element.setAttribute("action", event.target.href)
// 进行发送请求
element.submit();
}
</script>
5.2.2 处理器方法
public static final String LIST_ACTION = "redirect:/soldier";
public static final String PAGE_EDIT = "edit";
public static final String PAGE_LIST = "list";
@Autowired
private SoldierService soldierService;
@DeleteMapping("/{id}")
public String deleteById(@PathVariable("id") Integer soldierId){
soldierService.deleteById(soldierId);
// 重新查询所有,重定向查询所有方法
return LIST_ACTION;
}
5.2.3 web.xml中添加HiddenHttpMethodFilter 请求方式映射
<!--一定要配置在解决乱码的Filter之后-->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
6.跳转到添加数据的页面
list.html修改跳转add.html的路径 处理器方法无需修改
<tr>
<td colspan="5">
<a th:href="@{/add.html}">添加士兵</a>
</td>
</tr>
7.执行添加
7.2.1 处理器方法修改 添加和更新整合成一个方法
@PutMapping
public String addOrUpdate(Soldier soldier){
soldierService.addOrUpdate(soldier);
return LIST_ACTION;
}
7.2.3 SoldierService接口中添加方法
/**
* 新增或修改
* @param soldier
*/
void addOrUpdate(Soldier soldier);
7.2.4 SoldierServiceImpl实现类添加方法
@Transactional
@Override
public void addOrUpdate(Soldier soldier) {
// 判断是否携带id 未携带id是新增士兵
if (null == soldier.getSoldierId()){
Soldier soldierDaoByName = null;
try {
soldierDaoByName = soldierDao.findByName(soldier.getSoldierName());
} catch (Exception e) {
e.printStackTrace();
}
// 判断是否存在 不存在则添加
if (null == soldierDaoByName){
soldierDao.add(soldier);
}else {
return;
}
}else{
// 更新士兵
soldierDao.update(soldier);
}
}
7.2.5 修改add.html post方式通过服务端请求方式映射为put方式
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>添加士兵页面</title>
</head>
<body>
<form th:action="@{/soldier}" method="post">
<input type="hidden" name="_method" value="put">
士兵名称: <input type="text" name="soldierName"> <br>
士兵武器:<input type="text" name="soldierWeapon"> <br>
<button>提交</button>
</form>
</body>
</html>
8.跳转到更新数据页面
8.1 处理器方法
@GetMapping("/{id}")
public String getSoldierById(@PathVariable("id") Integer soldierId, Model model){
Soldier soldier = soldierService.getSoldierById(soldierId);
model.addAttribute("soldier", soldier);
return PAGE_EDIT;
}
8.2 list.html页面修改跳转edit.html路径
<td>
<a th:href="@{/soldier/}+${soldier.soldierId}">修改</a>
</td>
8.3 页面回显数据 post方式通过服务端请求方式映射为put方式
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>编辑士兵页面</title>
</head>
<body>
<form th:action="@{/soldier}" method="post">
<input type="hidden" name="_method" value="put">
<input type="hidden" name="soldierId" th:value="${soldier.soldierId}">
士兵姓名:<input type="text" name="soldierName" th:value="${soldier.soldierName}"> <br>
士兵武器:<input type="text" name="soldierWeapon" th:value="${soldier.soldierWeapon}"> <br>
<button>提交</button>
</form>
</body>
</html>
9.执行更新
处理器方法无需修改 和添加功能共用一个方法
@PutMapping
public String addOrUpdate(Soldier soldier){
soldierService.addOrUpdate(soldier);
return LIST_ACTION;
}
第五节 REST的小结
- 解决什么问题: 从根据功能设计url变成根据资源设计url
- 是什么:资源状态转移Representational State Transfer
- 特征:
- 一个url就代表一个资源,也就是说通过url我们可以知道当前请求操作的是什么资源
- 请求方式代表操作,也就是说通过请求方式我们知道当前请求对当前资源做的是何种操作
- 效果:
- url更加简洁
- url更加隐晦
- 操作的幂等性
- 怎么实现:
- 请求方式映射:HTML默认只能发送GET和POST请求,SpringMVC在某些时候需要将POST请求映射成PUT或者DELETE请求
- GetMapping、PostMapping、DeleteMapping、PutMapping要选择情况使用
- 合理设计url
- 使用Pathvariable注解获取REST风格的url上的路径参数
第二章 Ajax交互
第一节 获取请求参数
1.获取普通类型参数(与之前获取请求参数的方式一致)
1.1 引入JavaScript库
1.2 前端代码 WEB-INF下创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/static/js/vue.js"></script>
<script src="/static/js/axios.js"></script>
</head>
<body>
<div id="app">
<a href="javascript:;" @click="sendCommonParams">发送异步请求携带普通类型的参数</a> <br>
</div>
<script>
new Vue({
el:"#app",
data:{
},
methods:{
// 使用axios发送异步请求,并且携带普通类型的请求参数
sendCommonParams(){
axios({
"url":"/user/commonParams",
"method":"POST",
"params":{
"age":20,
"name":"zs",
"address":"sz"
}
}).then(response=>{
console.log(response.data.data)
})
}
}
})
</script>
</body>
</html>
1.3 后端代码
1.3.1 导入依赖
<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
</dependencies>
1.3.2 创建springmvc配置文件
<?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.atguigu"></context:component-scan>
<!-- mvc-->
<mvc:annotation-driven></mvc:annotation-driven>
<mvc:default-servlet-handler></mvc:default-servlet-handler>
</beans>
1.3.3 创建logback.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、行数、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%line] [%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT"/>
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="org.springframework.web.servlet.DispatcherServlet" level="DEBUG"/>
</configuration>
1.3.4 web.xml文件种添加dispatch处理器和处理乱码问题配置
<?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>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--CharacterEncodingFilter-->
<!-- 配置过滤器解决 POST 请求的字符乱码问题 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- encoding参数指定要使用的字符集名称 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 请求强制编码 -->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!-- 响应强制编码 -->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
1.3.5 创建User实体类
package com.atguigu.pojo;
import lombok.Data;
@Data
public class User {
private Integer age;
private String name;
private String address;
}
1.3.6 创建UserController类
package com.atguigu.controller;
import com.atguigu.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/commonParams")
public String commonParameter(User user){
// 获取异步请求携带的普通类型参数,和以前获取同步请求携带的参数是一样的
System.out.println("user========>" + user);
return "success";
}
}
2. 获取JSON请求体参数
2.1 前端代码
<a href="javascript:;" @click="sendJsonParams">发送异步请求携带JSON类型的参数</a> <br>
// 使用axios发送异步请求,携带JSON类型的请求参数
sendJsonParams(){
axios({
"url":"/user/jsonParams",
"method":"POST",
"data":{
"age":30,
"name":"ls",
"address":"sz"
}
}).then(response=>{
console.log(response.data.data)
})
}
2.2 后端代码
2.2.1 jackson依赖
如果忘记导入jackson依赖,会看到下面的错误页面
关于 SpringMVC 和 Jackson jar包之间的关系,需要注意:当 SpringMVC 需要解析 JSON 数据时就需要使用 Jackson 的支持。但是 SpringMVC 的 jar 包并没有依赖 Jackson,所以需要我们自己导入。
我们自己导入时需要注意:SpringMVC 和 Jackson 配合使用有版本的要求。二者中任何一个版本太高或太低都不行。
SpringMVC 解析 JSON 数据包括两个方向:
- 从 JSON 字符串到 Java 实体类。
- 从 Java 实体类到 JSON 字符串。
另外,如果导入了 Jackson 依赖,但是没有开启 mvc:annotation-driven 功能,那么仍然会返回上面的错误页面。
也就是说,我们可以这么总结 SpringMVC 想要解析 JSON 数据需要两方面支持:
- mvc:annotation-driven
- 引入 Jackson 依赖
还有一点,如果运行环境是 Tomcat7,那么在 Web 应用启动时会抛出下面异常:
org.apache.tomcat.util.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 19
解决办法是使用 Tomcat8 或更高版本。
2.2.2 处理器方法
@RequestMapping("/jsonParams")
public String jsonParams(@RequestBody User user){
// 1.获取Json请求体类型的参数必须封装到POJO对象或者是Map中
// 2.POJO参数或者Map参数的前面一定要加入@RequestBody注解,不然获取的值都为null
// 3.项目中一定要引入jackson的依赖
System.out.println("user========>" + user);
return "success";
}
第二节 响应JSON类型数据
1.前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/static/js/vue.js"></script>
<script src="/static/js/axios.js"></script>
</head>
<body>
<div id="app">
<a href="javascript:;" @click="sendCommonParams">发送异步请求携带普通类型的参数</a> <br>
<a href="javascript:;" @click="sendJsonParams">发送异步请求携带JSON类型的参数</a> <br>
</div>
<script>
new Vue({
el:"#app",
data:{
},
methods:{
// 使用axios发送异步请求,并且携带普通类型的请求参数
sendCommonParams(){
axios({
"url":"/user/commonParams",
"method":"POST",
"params":{
"age":20,
"name":"zs",
"address":"sz"
}
}).then(response=>{
console.log(response.data.data)
})
},
// 使用axios发送异步请求,携带JSON类型的请求参数
sendJsonParams(){
axios({
"url":"/user/jsonParams",
"method":"POST",
"data":{
"age":30,
"name":"ls",
"address":"sz"
}
}).then(response=>{
console.log(response.data.data)
})
}
}
})
</script>
</body>
</html>
2.后端代码
前提是项目中引入了jackson的依赖
创建result包及类
package com.atguigu.controller;
import com.atguigu.pojo.User;
import com.atguigu.result.Result;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/user")
public class UserController {
@ResponseBody
@RequestMapping("/commonParams")
public Result commonParameter(User user){
// 获取异步请求携带的普通类型参数,和以前获取同步请求携带的参数是一样的
System.out.println("user========>" + user);
return Result.ok(user);
}
@ResponseBody
@RequestMapping("/jsonParams")
public Result jsonParams(@RequestBody User user){
// 1.获取Json请求体类型的参数必须封装到POJO对象或者是Map中
// 2.POJO参数或者Map参数的前面一定要加入@RequestBody注解,不然获取的值都为null
// 3.项目中一定要引入jackson的依赖
System.out.println("user========>" + user);
// 将user封装到Result中 返回给客户端 必须添加ResponseBody注解
return Result.ok(user);
}
}
返回给客户端json格式的数据
3. 常见错误
3.1 500 错误
出现上面的错误页面,表示SpringMVC 为了将 实体类对象转换为 JSON 数据, 需要转换器。但是现在找不到转换器。它想要成功完成转换需要两方面支持:
- mvc:annotation-driven
- 引入Jackson依赖
3.2 406 错误(了解)
问题出现的原因:
- 请求地址扩展名:html
- 服务器端打算返回的数据格式:JSON
上面二者不一致。SpringMVC 要坚守一个商人的良心,不能干『挂羊头,卖狗肉』的事儿。解决办法有三种思路:
- 第一种方法:不使用请求扩展名
- 第二种方法:使用和实际返回的数据格式一致的扩展名
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.html</url-pattern>
<url-pattern>*.json</url-pattern>
</servlet-mapping>
- 第三种方法:使用一个 HTTP 协议中没有被定义的扩展名,例如:*.do
4. RestController注解
4.1 提取ResponseBody注解
如果类中每个方法上都标记了 @ResponseBody 注解,那么这些注解就可以提取到类上。
4.2 合并注解
类上的ResponseBody 注解可以和Controller 注解合并为RestController 注解。所以使用了RestController 注解就相当于给类中的每个方法都加了ResponseBody 注解。
4.3 RestController源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default "";
}