(一).响应数据
对于响应的数据,Http响应结果可以是数据,也可以是静态页面,也可以针对响应设置状态码,Header信息
1.返回静态页面
关于静态页面存放的位置,我们通常存放在"resources"包底下的"static"包中

java
@RestController
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/r1")
public String r1(){
return "/html1.html";
}
}

当我们访问的时候,发现,并没有返回页面
java
package org.review.blog.springbootblog.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Controller
@RequestMapping("/response")
public class ResponseController {
@RequestMapping("/r1")
public String r1(){
return "/html1.html";
}
}

但是,当我们将@RestController修改成@Controller之后,发现就可以正常访问了。
这是因为,随着互联网的发展,目前项目开发流行"前后端分离"模式,Java主要用于后端开发,所以不再处理前端相关的内容了,所以MVC的概念也逐渐发生了变化,View不再返回视图,而是返回显示视图时需要的数据。所以@RestController 其实是用来返回数据的。@RestController=@Controller+@ResponseBody,@Controller:定义一个控制器,在Spring框架启动时加载,把这个对象交给Spring来进行管理;@ResponseBody:定义返回的数据格式为"非视图",返回一个text/html信息。
总的来说,@ResponseBody是用来返回数据的,而不是用来返回视图的,如果想要返回视图,只需要@Controller就行了。
总结:如果一个类中既有返回页面的方法,又有返回数据的方法,则使用@Controller,在返回数据的方法上加上@ResponseBody
如果一个类全部返回数据,可以使用@RestController,或者是@Controller+@ResponseBody
如果一个类全部返回页面,则使用@Controller
java
@ResponseBody
@RequestMapping("/r2")
public String r2(){
return "返回数据需要用@ResponseBody";
}

2.返回前端片段(html类型)
java
@ResponseBody
@RequestMapping("/r3")
public String r3(){
return "<h1>返回h1标题</h1>";
}

可以看到,返回的页面,但是这个返回的页面是将<h1>当成了html标签来返回的

通过抓包工具可以看到,返回的格式为html类型
那么,如果我要返回的<h1>不是标签,而是文字,该如何解决?
事实上,我们直接修改返回的数据的格式类型即可
java
@ResponseBody
@RequestMapping(value = "/r3",produces = "text/plain")
public String r3(){
return "<h1>返回h1标题</h1>";
}

可以看到,我在@RequestMapping中加了一个produces属性,将返回的页面的格式由html类型转成了文本类型

通过抓包结果也可以看到
3.返回JSON
java
@ResponseBody
@RequestMapping("/r4")
public TestUserInfo r4(){
TestUserInfo userInfo=new TestUserInfo();
userInfo.setName("zhangsan");
userInfo.setAge(21);
userInfo.setGender(1);
return userInfo;
}
这里的@ResponseBody通过调用Jackson的消息转换器用ObjectMapper把java对象序列化成json字符串

重点:
@ResponBody 和 @RequestBody(上一章介绍到的)
@ResponBody为"响应体",负责把Java对象转为JSON,写入服务端发挥的响应体中
@RequstBody为"请求体",负责客户端发来的请求体中读JSON,转为Java对象
4.设置状态码
java
@ResponseBody
@RequestMapping("/r5")
public String r5(HttpServletResponse response){
response.setStatus(404); //通过setStatus()方法修改状态码
return "设置成功";
}

注意:我们手动设置的状态码的404,和真实的页面返回的404是不一样的,手动设置的状态码不会影响页面的展示
5.设置响应Header
java
@ResponseBody
@RequestMapping("/r6")
public String r6(HttpServletResponse response){
response.setHeader("myheader","header");
return "设置成功";
}

关于HttpServletRequest和HttpServletResponse
用户希望从http请求中获得什么,都可以从httpServletRequest中获取;
用户希望给客户端返回什么,都可以通过HttpServletResponse来返回
(二).再谈@RequestMapping
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {}; // 指定映射的URL 可以和path相互替换,当使用Requstmapping时只传递这一个属性的时候,value可以省略
@AliasFor("value")
String[] path() default {}; //可以和value进行替换
RequestMethod[] method() default {}; //指定请求的method类型,例如GET,POST等等
String[] params() default {}; //指定request中必须包含某些参数值,即参数绑定或必传
String[] headers() default {}; //指定request中必须包含某些指定的header值
String[] consumes() default {}; //指定处理request的提交内容类型,例如appliacation/json
String[] produces() default {}; //指定返回的内容类型,还可以同时设置返回值的字符编码
}
上面是@RequestMapping的原码,注释里面包含了属性的介绍
(三).具体案例
这里,只提供了几个简单的案例。同时,对于前端代码不做解释,因为俺也不会前端,主要是后端,前端代码俺也是复制粘贴的,重点的前端会做出一定的解释。
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/add" 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>
后端代码

可以发现,我后端的代码是分层调用的,这样做的目的在下面介绍到"三层架构"的时候再给具体介绍
关于这个计算器的案例,后端代码没有什么可以介绍的,后端只需要接收两个值就可以了,然后判断一下是否合法,如果合法的话就进行计算,如果不合法的话,就直接返回"参数不合法"即可


2.留言板

这是效果图
前端代码
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="js/jquery.min.js"></script>
<script>
$.ajax({
type: "get",
url: "message/getList",
success:function (messages){
if (messages!=null && messages.length>0){
var finalHtml="";
for(var msg of messages){
finalHtml+="<div>"+msg.from +"对" + msg.to + "说:" + msg.message+"</div>"
}
$(".container").append(finalHtml)
}
}
});
function submit(){
//1. 获取留言的内容
var from = $('#from').val();
var to = $('#to').val();
var say = $('#say').val();
if (from== '' || to == '' || say == '') {
return;
}
var data={
from:from,
to:to,
message:say
};
$.ajax({
type:"post",
url:"message/publish",
contentType:"application/json",
data:JSON.stringify(data), //JSON.stringify() 将对象转为JSON JSON.parse() //将JSON转为对象
success:function (result){
if (result.ok==1){ //由于后端返回的是JSON,所以前端可以直接从返回结果中取值
//成功
//2. 构造节点
var divE = "<div>"+from +"对" + to + "说:" + say+"</div>";
//3. 把节点添加到页面上
$(".container").append(divE);
//4. 清空输入框的值
$('#from').val("");
$('#to').val("");
$('#say').val("");
}else{
//失败
alert("留言发布失败")
}
}
});
}
</script>
</body>
</html>
在前后端交互的代码中,使用的ajax,大家可以自行了解
对于后端来说,要实现一个功能,就是当留言板的页面刷新的时候,之前留言的内容依旧在页面上
lombook介绍
在写后端代码之前,先给大家介绍一个依赖,这个依赖为"lombook"
"lombook"的作用主要是通过"注解"的方式,实现对类自动添加get(),set(),toString(),equals()等方法
添加lombook的方式有多种,第一种就是通过maven仓库,搜索lombook,然后选择对应的版本即可;另一种是在idea中下载一个插件,叫做"EditStarters"

这个插件的通就是可以直接导入Spring项目需要的依赖

通过上面的步骤,就将lambook依赖添加进来了

通过添加一个"@Data"注解,就可以不需要写get(),set()等方法了

上图是target文件中的MessageInfo类中的内容。
解释:target文件,即程序运行起来时,对应的.java文件

如果说,只想要让lombook生成get()和set()方法,不需要生成其他的方法,那么就可以将"@Data"注解,转换成"@Getter()"和"@Setter()"

如果只想给某个属性提供"get()"和"set()"方法,那么也可以单独给某个属性加这个注解

后端代码

可以看到,后端的代码也是是分层调用的
java
package org.review.blog.springbootblog.controller;
import org.review.blog.springbootblog.model.MessageInfo;
import org.review.blog.springbootblog.service.MessageService;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/message")
public class MessageController {
MessageService messageService=new MessageService();
@RequestMapping(value = "/publish",produces = "application/json")
public String publish(@RequestBody MessageInfo messageInfo){
return messageService.publish(messageInfo);
}
@RequestMapping("/getList")
public List<MessageInfo> getList(){
return messageService.getList();
}
}
这是,MessageController中的代码
java
@RequestMapping(value = "/publish",produces = "application/json")
上面这一行代码中的," produces="application/json" "的意思是,强制要求前端往后端传数据的时候,使用json格式进行传输,如果不适用json格式进行传输,则直接报错
java
public String publish(@RequestBody MessageInfo messageInfo)
@RequstBody为"请求体",负责客户端发来的请求体中读JSON,转为Java对象
java
package org.review.blog.springbootblog.dao;
import org.review.blog.springbootblog.model.MessageInfo;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class MessageDao {
List<MessageInfo> list=new ArrayList<>(); //用于保存结果
//发表留言,发表之后,使用list来统计发表的留言
public String publish(MessageInfo messageInfo){
if (!StringUtils.hasLength(messageInfo.getFrom())||
!StringUtils.hasLength(messageInfo.getTo())||
!StringUtils.hasLength(messageInfo.getMessage())){
return "{\"ok\": 0}";
}
list.add(messageInfo);
return "{\"ok\": 1}";
}
//获取全部留言
public List<MessageInfo> getList(){
return list;
}
}
上图是MessageDao中的代码
这个StringUtils,可以理解为是"String" 的一个工具类,然后"hasLength()"方法

当点开原码的时候,发现hasLength()方法,主要是判断字符串是否为空的
关于retrun的返回值,也是为了响应后端要给前端返回JSON格式的数据而设计的

现在,即使我再刷新,之前的留言数据也不会消失
3.图书系统
(1).功能介绍


总体来说,我们只需要实现两个功能,一个是用户登录,一个是图书的列表展示
(2).前端代码
Ⅰ.登录页
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>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/login.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
</head>
<body>
<div class="container-login">
<div class="container-pic">
<img src="pic/computer.png" width="350px">
</div>
<div class="login-dialog">
<h3>登陆</h3>
<div class="row">
<span>用户名</span>
<input type="text" name="userName" id="userName" class="form-control">
</div>
<div class="row">
<span>密码</span>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="row">
<button type="button" class="btn btn-info btn-lg" onclick="login()">登录</button>
</div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script>
function login() {
$.ajax({
type:"post",
url:"user/load",
data:{
name:$("#userName").val(),
password:$("#password").val()
},
success:function (result){
if (result){
//账号密码正确
location.href="book_list.html"
}else{
alert("账号密码错误")
}
}
});
}
</script>
</body>
</html>
Ⅱ.图书列表页
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图书列表展示</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/list.css">
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script src="js/jq-paginator.js"></script>
</head>
<body>
<div class="bookContainer">
<h2>图书列表展示</h2>
<div class="navbar-justify-between">
<div>
<button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button>
<button class="btn btn-outline-info" type="button" onclick="batchDelete()">批量删除</button>
</div>
</div>
<table>
<thead>
<tr>
<td>选择</td>
<td class="width100">图书ID</td>
<td>书名</td>
<td>作者</td>
<td>数量</td>
<td>定价</td>
<td>出版社</td>
<td>状态</td>
<td class="width200">操作</td>
</tr>
</thead>
<tbody>
<!-- <tr>-->
<!-- <td><input type="checkbox" name="selectBook" value="1" id="selectBook" class="book-select"></td>-->
<!-- <td>1</td>-->
<!-- <td>大秦帝国第一册</td>-->
<!-- <td>我是作者</td>-->
<!-- <td>23</td>-->
<!-- <td>33.00</td>-->
<!-- <td>北京出版社</td>-->
<!-- <td>可借阅</td>-->
<!-- <td>-->
<!-- <div class="op">-->
<!-- <a href="book_update.html?bookId=1">修改</a>-->
<!-- <a href="javascript:void(0)" onclick="deleteBook(1)">删除</a>-->
<!-- </div>-->
<!-- </td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td><input type="checkbox" name="selectBook" value="1" id="selectBook" class="book-select"></td>-->
<!-- <td>2</td>-->
<!-- <td>大秦帝国第二册</td>-->
<!-- <td>我是作者</td>-->
<!-- <td>23</td>-->
<!-- <td>33.00</td>-->
<!-- <td>北京出版社</td>-->
<!-- <td>可借阅</td>-->
<!-- <td>-->
<!-- <div class="op">-->
<!-- <a href="book_update.html?bookId=2">修改</a>-->
<!-- <a href="javascript:void(0)" onclick="deleteBook(2)">删除</a>-->
<!-- </div>-->
<!-- </td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td><input type="checkbox" name="selectBook" value="1" id="selectBook" class="book-select"></td>-->
<!-- <td>3</td>-->
<!-- <td>大秦帝国第三册</td>-->
<!-- <td>我是作者</td>-->
<!-- <td>23</td>-->
<!-- <td>33.00</td>-->
<!-- <td>北京出版社</td>-->
<!-- <td>可借阅</td>-->
<!-- <td>-->
<!-- <div class="op">-->
<!-- <a href="book_update.html?bookId=3">修改</a>-->
<!-- <a href="javascript:void(0)" onclick="deleteBook(3)">删除</a>-->
<!-- </div>-->
<!-- </td>-->
<!-- </tr>-->
<!-- <tr>-->
<!-- <td><input type="checkbox" name="selectBook" value="1" id="selectBook" class="book-select"></td>-->
<!-- <td>4</td>-->
<!-- <td>大秦帝国第四册</td>-->
<!-- <td>我是作者</td>-->
<!-- <td>23</td>-->
<!-- <td>33.00</td>-->
<!-- <td>北京出版社</td>-->
<!-- <td>可借阅</td>-->
<!-- <td>-->
<!-- <div class="op">-->
<!-- <a href="book_update.html?bookId=4">修改</a>-->
<!-- <a href="javascript:void(0)" onclick="deleteBook(4)">删除</a>-->
<!-- </div>-->
<!-- </td>-->
<!-- </tr>-->
</tbody>
</table>
<div class="demo">
<ul id="pageContainer" class="pagination justify-content-center"></ul>
</div>
<script>
getBookList();
function getBookList() {
$.ajax({
type:"post",
url:"book/getList",
success:function (books){
var finalHtml=""
for(var book of books){
//进行字符串拼接
finalHtml+='<tr>'
finalHtml+='<td><input type="checkbox" name="selectBook" value="1"'+book.bookId+' id="selectBook" class="book-select"></td>'
finalHtml+='<td>'+book.bookId+'</td>'
finalHtml+='<td>'+book.bookName+'</td>'
finalHtml+='<td>'+book.author+'</td>'
finalHtml+='<td>'+book.num+'</td>'
finalHtml+='<td>'+book.price+'</td>'
finalHtml+='<td>'+book.publish+'</td>'
finalHtml+='<td>'+book.status+'</td>'
finalHtml+='<td><div class="op">'
finalHtml+='<a href="book_update.html?bookId='+book.bookId+'">修改</a>'
finalHtml+='<a href="javascript:void(0)" onclick="deleteBook('+book.bookId+')">删除</a>'
finalHtml+='</div></td></tr>'
}
$("tbody").append(finalHtml)
}
});
}
//翻页信息
$("#pageContainer").jqPaginator({
totalCounts: 100, //总记录数
pageSize: 10, //每页的个数
visiblePages: 5, //可视页数
currentPage: 1, //当前页码
first: '<li class="page-item"><a class="page-link">首页</a></li>',
prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页<\/a><\/li>',
next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页<\/a><\/li>',
last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页<\/a><\/li>',
page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',
//页面初始化和页码点击时都会执行
onPageChange: function (page, type) {
console.log("第"+page+"页, 类型:"+type);
}
});
function deleteBook(id) {
var isDelete = confirm("确认删除?");
if (isDelete) {
//删除图书
alert("删除成功");
}
}
function batchDelete() {
var isDelete = confirm("确认批量删除?");
if (isDelete) {
//获取复选框的id
var ids = [];
$("input:checkbox[name='selectBook']:checked").each(function () {
ids.push($(this).val());
});
console.log(ids);
alert("批量删除成功");
}
}
</script>
</div>
</body>
</html>
(3).后端代码
Ⅰ.登录页

java
package org.review.blog.springbootblog.service;
import jakarta.servlet.http.HttpSession;
import org.review.blog.springbootblog.dao.UserDao;
public class UserService {
private UserDao userDao=new UserDao();
public Boolean load(String name, String password, HttpSession session){
Boolean load = userDao.load(name, password);
if(load==true){
//将用户名放到Session中
session.setAttribute("name",name); //创建一个会话
return true;
}else{
return false;
}
}
}
这是UserService类中的代码
当从userDao.load()返回true之后,说明用户名和密码都正确了,此时站在服务器的角度,就需要创建一个会话,然后保存下用户的基本信息。
java
package org.review.blog.springbootblog.dao;
import org.springframework.util.StringUtils;
public class UserDao {
public Boolean load(String name,String password){
if (!StringUtils.hasLength(name) ||
!StringUtils.hasLength(password)){
return false;
}
//校验用户名和密码是否正确
if ("admin".equals(name) && "admin".equals(password)){
return true;
}
return false;
}
}
这是UserDao类中的代码。在进行用户名和密码的校验的时候,我是把"admin"写在了前面,即把常量字符串写在了前面。这是因为,对于equals()来说,是会发生"空指针异常"的,即"NullpointException",所以将常量字符串写在了前面
测试
密码正确


密码错误

Ⅱ.图书列表页

由于我们现在还没有介绍到数据库,所以在Dao层中创建的数据一些数据是一些假数据
测试

(四).应用分层
1.具体介绍
在前面写案例的时候,可以发现,我的代码都是按照层次分的

可以看到,都是有层次的。这就是应用分层。
应用分层主要是出自于"阿里巴巴开发手册"中。

应用分层是一种软件开发思想,将程序分成N个层次,这N个层次分别负责各自的职责,多个层次之间协同提供完整的功能。
咱们现在介绍的Spring MVC ,就是把整体的系统分成了 Model,View,Controller 三个层次,也就是将用户试图和业务处理隔离开,并且通过控制器连接起来,很好地实现了变现和逻辑的解耦,是一种标准的软件分层架构。

目前,更主流的方式是"前后端分离"的方式,后端不关心前端的实现。所以对于Java后端来说,又有了一种新的分层架构,把整体分为表示层,业务逻辑层,数据层 ,这种方式也称为"三层架构"
表示层对应的就是Controller层,主要负责和客户端进行交互,包括参数的接收,数据的返回
业务逻辑层对应的就是Service层,负责处理业务逻辑,里面有复杂业务的具体实现
数据层对应的就是Dao层,负责存储和管理数据


上图是访问的流程

上图是MVC和三层架构的层次对应关系。二者实现的共同目的就是"解耦","分层","代码复用"
2.高内聚 和 低耦合
高内聚:一个模块中各个元素之间的联系的紧密程度,如果各个元素之间的联系程度越高,则内聚性越高,则"高内聚"。
低耦合:软件中各个层、模块之间的依赖关联程序越低越好。修改一处代码,其他模块的代码改动越少越好。

下面,通过一个例子来介绍"高内聚"和"低耦合"

上图,是一个公司的部门分布。
此时,如果"财务人员1"家里有事,请假了,那么"财务人员2","财务人员3","财务人员4",这三个人都可以随时顶上,完成"财务人员1"的工作,此时就称为"高内聚"。
此时,即使今天"财务部门"的所有人员都请假了,那么也不会影响"行政部门"的人继续工作,此时就称"低耦合"。
可以发现,"高内聚"和"低耦合"并不矛盾。"高内聚"指的是一个模块中各个元素之间的联系紧密程度,例如"财务部门"的联系紧密程度;"低耦合"指的是各个模块之间的紧密程度,例如"行政部门"的联系紧密程度。
(五).总结
在介绍Spring MVC的时候,都是介绍各种Web开发需要用到的注解。
1.@RequestMapping :路由映射
2.@RequestParam :参数重命名
3.@RequestBody :接收JSON类型的数据
4.@PathVariable :接收路由参数
5.@ReuqestPart :上传文件
6.@ResponseBody :返回数据
7.@CookieValue :从Cookie中获取值
8.@SessionAttribute:从Session中获取值
9.@RequestHeader:从Header中获取值
10.@Controller :定义一个控制器,Spring框架启动时加载,把这个对象交给Spring管理,默认返回视图
11.@RestController=@Controller + @ResponseBody