一.响应
在之前我们学习了如何在请求中传递参数以及获取到请求中的一些信息,接下来我们要了解HTTTP中的响应中的各种数据,http的响应可以是数据,也可以是静态页面,也可以针对响应设置状态码,Header信息等.
1.返回静态页面
先创建一个html页面
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index⻚⾯</title>
</head>
<body>
Hello,Spring MVC,
我是Index⻚⾯.
</body>
</html>
页面具体内容如下:

后端代码实现:
java
@Controller
public class IndexController {
@RequestMapping("html")
public Object returnHtml(){
return "index.html";
}
}

这里我们要注意一点,@RestController和@Controller注解的区别:
@RestController是用来返回数据的,@Controller是用来返回页面的.
打开@RestController的源码,可以看到@RestController = @Controller + @ResponseBody
@RseponseBody是用来返回数据的.

当我们在刚刚的代码上加上@ResponseBody之后,就会返回数据,

2.返回数据@ResponseBody
上面我们提到,@ResponseBody注解是用来返回数据的.在刚刚的返回HTML页面的代码上加上这个注解,这个方法就会把"index.html"当做数据返回给前端.
@ResponseBody即是类注解,也是方法注解,如果加载类上,就表示该类的所有方法都是返回数据,如果作用在方法上,就表示这个方法返回的是数据.
如果一个类的方法里面,又返回页面,又返回数据,我们可以在返回数据的方法上面添加@ResponseBody.

如果想返回数据,但是不加这个注解,就会报错

3.返回HTML代码片段
后端在返回数据的时候,如果数据中包含HTML代码片段,也会被浏览器解析

用Fiddler抓包,可以看到响应类型是text/html

4.返回JSON
SpringMVC也可以返回JSON数据.
java
@RequestMapping("returnJSON")
public HashMap<String,String> returnJSON(){
HashMap<String,String> map = new HashMap<>();
map.put("zhangsan","1号");
map.put("lisi","2号");
return map;
}

通过抓包可以看到,返回类型确实为JSON

5.设置状态码
SpringMVC会根据我们方法返回的结果自动设置响应状态码,程序员也可以手动设置.


通过抓包我们可以看到状态码确实为404,但是这并不影响页面展示.
6.设置Header
http响应报头也会向客户端传递一些信息,比如服务程序的名称,请求资源已经移动到新地址等等.
这些信息通过@RequestMapping注解的属性来实现.
来看看@RequestMapping注解的源码:

1.value指定映射的URL路径.
2.method指定请求的method类型,比如GET,POST请求.
3.consumes:指定处理请求的提交内容类型(Content-Type),例如application/json,text/html.
4.produces:指定返回的内容类型,还可以同时设置返回值的字符编码.
5.Params:指定request中必须包含某些参数值才可以处理.
6.headers:指定request中必须包含某些特定的header值,才能让该方法处理请求.
指定JSON类型,将produces指定成application/json就可以让内容转变为JSON格式.

不指定就是字符串返回,具体可以去看Fiddler抓包结果.
我们还可以自己添加新的header.使用HttpServletResponse实现.
java
@RequestMapping("setHeader2")
public String setHeader2(HttpServletResponse response){
response.setHeader("school","dingdua");
return "设置成功";
}
不过这个设置不能是中文,否则会不显示.

二.综合性练习
在学习完上述内容之后,我们可以做一些小练习了.用来帮我们巩固上面的知识.
1.加法计算器
需求:输入两个整数,点击按钮,就可以显示计算结果.
前端代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="calc/sum" method="post">
<h1>计算器</h1>
数字1:<input name="num1" type="text"><br>
数字2:<input name="num2" type="text"><br>
<input type="submit" value=" 点击相加 ">
</form>
</body>
</html>
就是这么一个简单的页面,前端代码不需要大家会,只需要差不多看得懂就可以

准备工作:将html前端代码放在static目录下,

约定前后端交互接口:


后端代码:
java
@RestController
@RequestMapping("/calc")
public class CalcController {
@RequestMapping("/add")
public String add(Integer num1,Integer num2){
Integer sum = num1 + num2;
return "<h1>计算结果:" + sum + "</h1>";
}
}
注意:后端代码的URL路径要和前端代码中的访问URL路径一致才能访问到.
比如后端URL路径是:/calc/add
前端红色框框柱的就是前端点击按钮之后要跳转的资源路径,method表示请求类型: 
同时我们要注意蓝色方框框出来的num1和num2两个变量名要和后端方法中接收的参数名一致,才能接收到数据.
测试成功,运行正常:


2.用户登入
需求:用户输入账号和密码,后端校验是否正确,正确则跳转页面,后续再次访问就可以获取到登入用户信息,不正确则前端告知用户.
前端代码:
index.html(登入界面)
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>用户登录首页</title>
</head>
<body>
登录人: <span id="loginUser"></span>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
login.html(输入界面):
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>用户登录</h1>
用户名:<input name="userName" type="text" id="userName"><br>
密码:<input name="password" type="password" id="password"><br>
<input type="button" value="登录" onclick="login()">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
function login() {
}
</script>
</body>
</html>
约定前后端交互接口:
需求分析:对于后端开发人员来说,不涉及前端页面的展示,只需要提供两个功能就行.
1.登入页面:通过输入账号密码,校验是否正确,告诉前端.
2.首页:告知前端当前登入用户,如果当前已有用户登入,返回登入的账号,如果没有,返回空.
校验接口定义:
请求路径:/user/login
请求方式:POST
接口描述:判断账号密码是否正确.
请求参数:
userName - String类型 - 必填 - 账号
password - String类型 - 必填 - 密码.
响应数据:
Content - Type:text/html
响应内容:true / false
查询登入用户接口定义:
请求路径:/user/getLoginUser
请求方式:GET
接口描述:查询当前登入用户
请求参数:无
响应数据:
Content-Type: text/html
响应内容: 用户名称
后端代码:
1.校验接口:
主要逻辑就是先判断账号密码是否为空,然后判断账号密码是否正确,正确的话就将用户名存储在session中,用于后续首页展示用户名.
java
@RestController
@RequestMapping("/user")
public class LoginController {
@RequestMapping("/login")
public Boolean login(String userName, String password, HttpSession session){
//判断账号或者密码是否为空
if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
return false;
}
//TODO判断账号密码是否正确,由于这部分没学习数据库相关知识,暂时搁置
//直接用固定账号密码来判断
if (!("admin".equals(userName) && "123456".equals(password))) {
return false;
}
//登录成功,将用户名保存到session中
session.setAttribute("loginUser",userName);
return true;
}
2.首页接口:
通过session获取到正确登入的用户的用户名,然后展现在前端页面中
java
@RestController
@RequestMapping("/user")
public class getLoginUser {
@RequestMapping("/getLoginUser")
public String getLoginUser(HttpSession session){
//从session中获取用户名称
String username = (String) session.getAttribute("loginUser");
return username;
}
前段代码调整:
index.html页面增加:
html
<script>
$.ajax({
type:"get",
url:"/user/getLoginUser",
success:function (result) {
$("#loginUser").text(result)
}
})
login.html增加:
html
function login() {
$.ajax(
{
type: 'POST',
url: '/user/login',
data: {
userName: $('#userName').val(),
password: $('#password').val()
},
success: function (data) {
if (data) {
location.href = '/index.html'
} else {
alert("账号或密码错误")
}
}
}
)
}
前端页面可以借助AI工具帮助理解.
运行测试:
输入错误账号密码会提示

输入正确账号密码会跳转到index.html页面,并显示用户名:

3.留言板

需求:输入留言信息,点击提交,后端可以把数据保存起来,页面展示输入的留言板的信息
前端代码:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>留言板</title>
<style>
.container {
width: 350px;
height: 300px;
margin: 0 auto;
/* border: 1px black solid; */
text-align: center;
}
.grey {
color: grey;
}
.container .row {
width: 350px;
height: 40px;
display: flex;
justify-content: space-between;
align-items: center;
}
.container .row input {
width: 260px;
height: 30px;
}
#submit {
width: 350px;
height: 40px;
background-color: orange;
color: white;
border: none;
margin: 10px;
border-radius: 5px;
font-size: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>留言板</h1>
<p class="grey">输入后点击提交, 会将信息显示下方空白处</p>
<div class="row">
<span>谁:</span> <input type="text" name="" id="from">
</div>
<div class="row">
<span>对谁:</span> <input type="text" name="" id="to">
</div>
<div class="row">
<span>说什么:</span> <input type="text" name="" id="say">
</div>
<input type="button" value="提交" id="submit" onclick="submit()">
<!-- <div>A 对 B 说: hello</div> -->
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
function submit(){
//1. 获取留言的内容
var from = $('#from').val();
var to = $('#to').val();
var say = $('#say').val();
if (from== '' || to == '' || say == '') {
return;
}
//2. 构造节点
var divE = "<div>"+from +"对" + to + "说:" + say+"</div>";
//3. 把节点添加到页面上
$(".container").append(divE);
//4. 清空输入框的值
$('#from').val("");
$('#to').val("");
$('#say').val("");
}
</script>
</body>
</html>
前端代码能做到点击提交按钮之后,信息在下面出现,但是刷新之后这些记录就会消失,
后端做的就是用户在输入信息之后,后端要保存信息,页面展示的时候,需要从后端获取到所有的留言信息.
后端代码:
先创建一个MessageInfo类,用来表示一条消息
java
@Data
public class MessageInfo {
//一个MessageInfo对象对应数据库中的一条记录
private String from;
private String to;
private String say;
}
这里有个我们没有见过的@Data注解,这个注解是用来帮助我们直接添加各种属性的get/set方法.
我们可以在pom.xml文件里面添加lombok依赖:
java
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
也可以使用插件EditStarter帮助我们快速添加依赖,这个大家可以去了解下
在创建完一个消息对象之后,创建一个队列,用来表示消息记录,每次点击按钮,前端就会发送请求到后端,后端保存这些聊天记录,然后当前端下次刷新的时候,就会先从后端拿到聊天记录,然后再展示到前端.
java
@RestController
@RequestMapping("/message")
public class MessageController {
List<MessageInfo> messageList = new ArrayList<>();
//每次点击提交按钮的时候,通过getMessage方法,从session中获取用户名称,并返回给前端
@RequestMapping("/getMessage")
public List<MessageInfo> getMessage(){
return messageList;
}
@RequestMapping("pushMessage")
public Boolean pushMessage(MessageInfo messageInfo){
System.out.println(messageInfo);
if(StringUtils.hasLength(messageInfo.getFrom())&&StringUtils.hasLength(messageInfo.getTo())&&StringUtils.hasLength(messageInfo.getSay())){
messageList.add(messageInfo);
return true;
}
return false;
}
}
后端的逻辑很简单,基本上都能看懂.然后我们需要补充下前端代码:
添加load()方法,让前端加载的时候就会从后端读取消息列表然后展示到前端页面
html
$(function () {
load();
});
function load(){
$.ajax({
url: "/message/getMessage",
type: "get",
success: function (res) {
for (var i = 0; i < res.length; i++) {
var divE = "<div>" + res[i].from + "对" + res[i].to + "说:" + res[i].say + "</div>";
$(".container").append(divE);
}
}
});
}
修改submit()方法,将请求传递到后端pushMessage(),当留言成功就展示到页面上,留言失败就弹框出来.
html
function submit(){
//1. 获取留言的内容
var from = $('#from').val();
var to = $('#to').val();
var say = $('#say').val();
if (from== '' || to == '' || say == '') {
return;
}
$.ajax({
url: "/message/pushMessage",
type: "post",
data: {
from: from,
to: to,
say: say
},
success: function (res) {
if(res){
//2. 构造节点
var divE = "<div>"+from +"对" + to + "说:" + say+"</div>";
//3. 把节点添加到页面上
$(".container").append(divE);
//4. 清空输入框的值
$('#from').val("");
$('#to').val("");
$('#say').val("");
}else{
alert("发表留言失败!");
}
}
});
}
运行测试:
结果符合预期,每次刷新/重新打开页面都会从服务器加载数据,因此关闭页面数据也不会丢失.
