目录
1.安装插件 EditStarter,重启 IDEA编辑编辑
2.在pom.xml 文件中,单击右键,选择 Generate,操作如下图所示
书接上文:Java EE 进阶:4.Spring Web MVC 入门(第四弹)~~
3.3 留言板
在学习前端的时候,我们就写过这里的代码,只不过是纯页面的👇,没有前后端交互,刷新一下页面留言的内容就没了
Java EE 进阶:2.3 JavaScript 综合案例
现在我们已经学习后端了,接下来我们来完成前后端交互,存储一下留言的内容~~
需求:界面如下图所示
1.输入留言信息,点击提交,后端把数据存储起来
2.页面展示输入的表白墙的信息
3.3.1 准备工作
把前端页面放在项目中(课件中提供)
码云地址:JavaEE 进阶课程资料包
3.3.2 约定前后端交互接口
需求分析
后端需要提供两个服务
1.发表留言接口:让前端把这些数据传过来,我们把它存储下来
2.留言列表接口:刷新或者点击提交的时候,下面要显示一个列表
接口定义
1.获取全部留言
全部留言信息,我们用 List 来表示,可以用 JSON 来描述这个 List 数据
请求:
GET /message/getList响应:JSON 格式
[ { "from": "黑猫", "to": "白猫", "message": "喵" }, { "from": "黑狗", "to": "白狗", "message": "汪" }, //... ]浏览器给服务器发送一个 GET / message / getList 这样的请求,就能返回当前一共有哪些留言记录.结果以 json 的格式返回过来
2.发表新留言
请求:body 也为 JSON 格式
POST /message/publish { "from": "黑猫", "to": "白猫", "message": "喵" }响应:JSON 格式
{ ok: 1 }我们期望浏览器给服务器发送一个 POST / message / publish 这样的请求,就能把当前的留言提交给服务器
3.3.3 实现服务器端代码
3.3.3.1 lombok 介绍(重点)
我们去写一个对象的时候,要写它的属性、get 方法、set 方法,那对于这种重复性的工作,我们是否可以不做呢?比如这些构造方法我们可不可以不写,通过一些注解完成?咱们 Java 本身就有很多注解帮助我们开发的,虽然现在 get 方法、set 方法我们现在也不写,直接右键直接生成了,但是我现在连生成就懒得生成了,虽然并不用写,但是看着这么多代码也嫌烦👇接下来我们就来学一个新的东西,这些东西就可以不写了~~
Lombok 是一个 Java 工具库,通过添加注解的方式,简化 Java 的开发
简单来学习下它的使用
1.引入依赖
lombok 是一个第三方的工具包,并不是 Spring 的,所以要先引入依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>直接复制到 xml 文件里👇点击刷新后会自动为我们导入依赖👇
当然,如果你不知道这个依赖是什么,咱就可以去 Maven 中央仓库去查询👇https://mvnrepository.com/search?q=lombok
第一个就是了,然后随便找一个使用次数多的版本(有漏洞的不要选,尽量选次新的),点进去复制 依赖 即可👇
粘贴到我们的 xml 文件里,顺序不分先后👇
2.使用
lombok 通过一些注解的方式,可以帮助我们消除一些冗长代码,使代码看起来简洁一些
比如之前的 Person 对象,就可以改为
package com.bite.spring.demo; import lombok.Data; @Data public class MessageInfo { private String from;//谁写的 private String to;//写给谁的 private String message;//内容是啥 }@Data 注解会帮助我们自动一些方法,包含 getter / setter,equals,toString 等
此时我们测试一下,发现自动就出来了 get、set 的方法👇
public void testLombok(){ MessageInfo messageInfo = new MessageInfo(); messageInfo. }
但如果此时将 @Data 注解给注释掉之后就会发现报错了👇
3.原理解释
可以观察加了 @Data 注解之后,IDEA 反编译的 class 文件,运行程序后,看 @Data 帮助我们生成了哪些方法👇
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.bite.spring.demo; public class MessageInfo { private String from; private String to; private String message; public MessageInfo() { } public String getFrom() { return this.from; } public String getTo() { return this.to; } public String getMessage() { return this.message; } public void setFrom(final String from) { this.from = from; } public void setTo(final String to) { this.to = to; } public void setMessage(final String message) { this.message = message; } public boolean equals(final Object o) { if (o == this) { return true; } else if (!(o instanceof MessageInfo)) { return false; } else { MessageInfo other = (MessageInfo)o; if (!other.canEqual(this)) { return false; } else { Object this$from = this.getFrom(); Object other$from = other.getFrom(); if (this$from == null) { if (other$from != null) { return false; } } else if (!this$from.equals(other$from)) { return false; } Object this$to = this.getTo(); Object other$to = other.getTo(); if (this$to == null) { if (other$to != null) { return false; } } else if (!this$to.equals(other$to)) { return false; } Object this$message = this.getMessage(); Object other$message = other.getMessage(); if (this$message == null) { if (other$message != null) { return false; } } else if (!this$message.equals(other$message)) { return false; } return true; } } } protected boolean canEqual(final Object other) { return other instanceof MessageInfo; } public int hashCode() { int PRIME = 59; int result = 1; Object $from = this.getFrom(); result = result * 59 + ($from == null ? 43 : $from.hashCode()); Object $to = this.getTo(); result = result * 59 + ($to == null ? 43 : $to.hashCode()); Object $message = this.getMessage(); result = result * 59 + ($message == null ? 43 : $message.hashCode()); return result; } public String toString() { String var10000 = this.getFrom(); return "MessageInfo(from=" + var10000 + ", to=" + this.getTo() + ", message=" + this.getMessage() + ")"; } }我们发现 @Data 变成了构造函数、get 和 set 方法、equals 方法、hasCode 方法、toString 方法~~
我们点进去 @Data 这个注解,会发现这个注解的生命周期是只存活在源码阶段,在 class 阶段已经变成了以上内容
那么我们以后就不用写 get 方法、set 方法了~~
闲聊:
这不是真正的字节码文件,而是 IDEA 根据字节码进行反编译后的文件
反编译是将可执行的程序代码转换为某种形式的高级编程语言,使其具有更易读的格式,反编译是一种逆向工程,它的作用与编译器的作用相反
可以看出来,lombok 是一款在编译期生成代码的工具包
Java 程序的运行原理:
Lombok 的作用如下图所示:
4.更多使用
有的时候我们并不希望它帮我生成 toString() 方法,这时候就需要其他注解了
如果觉得@Data比较粗暴(生成方法太多), lombok也提供了一些更精细粒度的注解
注解
作用
@Getter
自动添加 getter 方法
@Setter
自动添加 setter 方法
@ToString
自动添加 toString 方法
@EqualsAndHashCode
自动添加 equals 和 hashCode 方法
@NoArgsConstructor
自动添加无参构造方法
@AllArgsConstructor
自动添加全属性构造方法,顺序按照属性的定义顺序
@NonNull
属性不能为 null
@RequiredArgsConstructor
自动添加必需属性的构造方法,final + @NonNull 的属性为必需
@Data = @Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor
+ @NoArgsConstructor如果说咱们只想要 get 方法 和 set 方法,就可以这样写👇
package com.bite.spring.demo; //import lombok.Data; import lombok.Getter; import lombok.Setter; //@Data @Getter @Setter public class MessageInfo { private String from;//谁写的 private String to;//写给谁的 private String message;//内容是啥 }此时就只剩下 get 和 set 方法了👇
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.bite.spring.demo; public class MessageInfo { private String from; private String to; private String message; public MessageInfo() { } public String getFrom() { return this.from; } public String getTo() { return this.to; } public String getMessage() { return this.message; } public void setFrom(final String from) { this.from = from; } public void setTo(final String to) { this.to = to; } public void setMessage(final String message) { this.message = message; } }那如果说我不想全生成,我只想生成 from 的 get 和 set 方法,那就可以这么写👇
package com.bite.spring.demo; //import lombok.Data; import lombok.Getter; import lombok.Setter; //@Data //@Getter //@Setter public class MessageInfo { @Getter @Setter private String from;//谁写的 private String to;//写给谁的 private String message;//内容是啥 }此时就会只生成 from 的 get 和 set 方法👇
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.bite.spring.demo; public class MessageInfo { private String from; private String to; private String message; public MessageInfo() { } public String getFrom() { return this.from; } public void setFrom(final String from) { this.from = from; } }3.3.3.2 更快捷的引入依赖
上述引入lombok 依赖是比较通用的方式, 需要去 Maven 找 lombok 的依赖
接下来介绍更简单引入依赖的方式(注意,这种便捷式的添加依赖并不一定能找到所有依赖,但是上面那种通用方式是可以找到所有依赖的~~)1.安装插件 EditStarter,重启 IDEA
单击右键,看到这个就说明安装好了👇
2.在pom.xml 文件中,单击右键,选择 Generate,操作如下图所示
默认选 GitHub 就可以~~
进入Edit Starters的编辑界面, 添加对应依赖即可
点击 OK 之后就自动帮我们生成好了👇
这个好像跟刚才的不太一样,没带版本号,没有关系,因为我们不指定版本的话,SpringBoot 也已经帮我们指定了版本,把鼠标放在 lombok 上就能看到👇
在哪里进行管理的呢?在上面的 spring-boot-starter-parent 这里👇
点进去后,再点这个👇
再点进去,发现里面是有 Lombok 版本的👇
也就是说,我们没有指定版本,它就默认使用这个版本,如果我们指定了版本,它就按照我们的指定的版本
注意:
不是所有依赖都可以在这里添加的, 这个界面和SpringBoot创建项目界面一样.
依赖不在这里的, 还需要去Maven仓库查找坐标, 添加依赖.3.3.3.3 服务器代码实现
定义留言对象 MessageInfo 类
import lombok.Data; @Data public class MessageInfo { private String from; private String to; private String message; }创建 MessageController 类
使用List<MessageInfo> 来存储留言板信息package com.bite.spring.demo; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-10-24 * Time: 16:13 */ @RequestMapping("/message") @RestController public class MessageController { private List<MessageInfo> messageInfoList=new ArrayList<>(); @PostMapping(value = "/publish",produces = "application/json") public String publish(@RequestBody MessageInfo messageInfo){ //@RequestBody是从请求正文中拿数据,而JSON的请求数据就在请求正文中 //@ResponseBody是响应正文的数据 //先判断是否为空 if(!StringUtils.hasLength(messageInfo.getFrom()) ||!StringUtils.hasLength(messageInfo.getTo()) ||!StringUtils.hasLength(messageInfo.getMessage())){ return "{\"ok\":0}";//上面加上produces = "application/json"以转成JSON } //把传的请求放进来 messageInfoList.add(messageInfo); return "{\"ok\":1}"; } @GetMapping("/getList") public List<MessageInfo> getList(){ return messageInfoList; } }使用 Postman 自测一下,发现都没问题👇
在一开始的时候确实是空的,没问题
这时候我们再 get 一下,发现就能获取到了👇
答疑:不加 @RequestBody 会怎么样??
@RequestBody 表示请求参数为 JSON,我们把 @RequestBody 去掉后重新启动程序👇
咱们再来去发布一个 publish 👇
此时报了一个 0,在代码中走到这里了👇
if(!StringUtils.hasLength(messageInfo.getFrom()) ||!StringUtils.hasLength(messageInfo.getTo()) ||!StringUtils.hasLength(messageInfo.getMessage())){ return "{\"ok\":0}";//上面加上produces = "application/json"以转成JSON }也就是说我们的 messageInfo 这个值它拿到的是一个空的对象
下面我们打断点 Debug 调试一下,Debug 方式跟之前也是一样的👇打好断点之后点击上方的小虫子调试
等断点上的红点点出现 √ 时说明已经走到断点处了,此时我们再发送请求 Postman 就会一直加载👇
我们发现它确实没有拿到这个值,全都是 null👇
所以 @RequestBody 肯定是要加的~~
下面给大家说一下原理是什么
请求方式①:在 Body 里面发 JSON
此时我们抓包时,数据也是在 Body 这里,也是 JSON
请求方式②:普通对象就在 Params 里
我们发现 请求的参数全放在上面了👇
请求方式③:Body 的 x-www-form-urlencoded 部分
我们发现它的请求参数只是从上面挪到下面来了,但是它的结构方式时不一样的
第一种方式是 JSON 的方式,下面这两种方式是用 & 和 = 把参数连接起来的方式
所以我们的前端和后端是要对应上的,我们既然定义的是 JSON,那么我们刚才测试的时候也是使用这个来发起请求
3.3.4 调整前端页面代码
后端既然提供两个接口,那么前端也是要发两个请求的~~
闲聊:前端知识复习~~
JS 声明变量
var:早期声明变量的方式
let:最近流行的声明变量的方式
const:声明常量的方式
{} 表示对象,里面的属性都是 key-value 的形式
修改 messagewall.html
1.在点击按钮的时候给服务器发送添加留言请求
<!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; } var data={ from:from, to:to, message:say }; $.ajax({ type:"post", url:"/message/publish", contentType:"application/json", data:JSON.stringify(data),//把对象转成JSON success:function (result){ //如果后端发送的值是JSON,那么前端是可以直接当作对象来用的 if(result.ok==1){ //成功 //2. 构造节点 var divE = "<div>"+from +"对" + to + "说:" + say+"</div>"; //3. 把节点添加到页面上 $(".container").append(divE);//追加在后面 //4. 清空输入框的值 $('#from').val(""); $('#to').val(""); $('#say').val(""); }else{ //失败 alert("留言发布失败"); } } }); } </script> </body> </html>上述代码中涉及新的知识👇
并且存在着对应关系👇
服务运行后,我们来抓包测试一下👇
此时的抓包结果👇
我们通过 浏览器 抓包也是一样的👇
答疑:
Q1:如果把前端的 contentType:"application/json"去掉怎么样??👇
我们会发现 Fiddler 和 浏览器 说的都一样:这种类型不支持,就是这种类型是不对的👇
此时 Content-Type 类型为:application/x-www-form-urlencoded,类型对不上~~
Q2:那如果把后端的 produces="application/json" 去掉会怎么样??👇
我们当时为了改成 JSON,通过这种方式手动给改了,那如果不改,那么返回来的就是一个字符串,咱们再来测试👇
我们发现 Content-Type 是文本类型👇
那么文本类型为什么留言失败了??我们来分析一下👇
success:function (result){ if(result.ok==1){ //成功 //2. 构造节点 var divE = "<div>"+from +"对" + to + "说:" + say+"</div>"; //3. 把节点添加到页面上 $(".container").append(divE);//追加在后面 //4. 清空输入框的值 $('#from').val(""); $('#to').val(""); $('#say').val(""); }else{ //失败 alert("留言发布失败"); } } });我们发现肯定 result.ok ==1 为 false 了,但事实结果上咱们的状态确实是 result.ok =1,这咋回事儿??对于初学者,咱们最好的方式就是打印日志👇
success:function (result){ //打印日志观察两者类型的差异 console.log(result); console.log(result.ok); if(result.ok==1){ //成功 //2. 构造节点 var divE = "<div>"+from +"对" + to + "说:" + say+"</div>"; //3. 把节点添加到页面上 $(".container").append(divE);//追加在后面 //4. 清空输入框的值 $('#from').val(""); $('#to').val(""); $('#say').val(""); }else{ //失败 alert("留言发布失败"); } } });重新启动服务后再来观察👇
我们发现第一个没问题,但是第二个类型却是未定义类型,这是为什么呢??
因为当返回类型为 text/plain 的时候,会认为 {"ok":1} 是个字符串,那么它就是个整体,而 result.ok 的这个 ".",说明是个对象我们才能用 "." 找属性才能拿到值,那么现在是个字符串,不是对象,根本 "." 不出来,所以就会在这个地方发生错误
所以我们前后端,你接口定义的什么,就要按照那个规定去定义,不能说看结果都是 {"ok":1}一样就行了,Content-Type 类型也是要关注的
Q3:我有没有办法可以不改后端,就返回这个字符串,然后让前端运行成功??我们的前端可不可把这个 result 转换成 JSON,也就是能不能转换成对象??
我们上面学了把 对象 转成 JSON 👇
data:JSON.stringify(data),//把对象转成JSON当然,把 JSON 转成 对象 也是可以的👇
success:function (result){ // //打印日志观察两者类型的差异 // console.log(result); // console.log(result.ok); var jsonObj=JSON.parse(result);//JSON转对象 if(jsonObj.ok==1){ //成功 //2. 构造节点 var divE = "<div>"+from +"对" + to + "说:" + say+"</div>"; //3. 把节点添加到页面上 $(".container").append(divE);//追加在后面 //4. 清空输入框的值 $('#from').val(""); $('#to').val(""); $('#say').val(""); }else{ //失败 alert("留言发布失败"); } } });这时候就正常了👇
所以说我们的前后端是要相互配合的,前端也可以改后端也可以改,那如果测试人员真测出 Bug 了,这是前端改还是后端改呢??看接口文档,如果接口文档说的就是返回类型是 JSON,那么就是后端改,如果说的是返回类型为文本,那么就是前端改了~~
闲聊:要是边界划分不清晰的话,大概率会商量一下,哪边改容易就哪边改,咱们程序员这个圈子还是比较纯洁的~~
Q4:感觉每次都要重启服务好麻烦,有没有不吃操作的方式??
有的,这种方式叫”热部署“,但是这种方式也很麻烦,原理就是不用你重启了,IDEA 帮你重启了,但是也很麻烦,有时候你代码都改完了,它还没重启呢,机制大概就是等5秒,这5秒你没有任何操作,它才会重启,但往往咱等不了5秒~~大家感兴趣直接搜“热部署IDEA”就行了
2.添加 load 函数,用于在页面加载的时候获取数据
写到现在其实看着跟之前没啥区别,也是一刷新就没了,下面我们来学如何将列表存储下来~~
刷新的话其实就是获取留言列表,咱们的列表信息全在 messageInfoList 存着呢👇
所以我们只需要前端去调用 getList 这个接口,然后给它显示到页面上就可以了
我们希望在刷新的时候去显示留言列表,就是加载页面的时候获取留言列表信息,也就是已进入页面的时候就应该发送 Ajax 请求,这种页面一进来就需要加载的内容,我们就直接写在 script 下面👇
load();//封装 function load(){ $.ajax({ type:"get", url:"/message/getList", success:function(messages){ if(messages!=null&&messages.length>0){ var finalHtml=""; //类似Java的for(MessageInfo messageInfo:messageInfoList) for(var m of messages){ //给每一个元素都拼接上这样一行HTML代码 var item="<div>"+m.from+"对"+m.to+"说:"+m.message+"</div>"; //拼成一个大的HTML代码放在下面,字符串代码直接加就行 finalHtml+=item; } //放进去 $(".container").append(finalHtml); } } }); }完整代码👇
<!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> load();//封装 function load(){ $.ajax({ type:"get", url:"/message/getList", success:function(messages){ if(messages!=null&&messages.length>0){ var finalHtml=""; //类似Java的for(MessageInfo messageInfo:messageInfoList) for(var m of messages){ //给每一个元素都拼接上这样一行HTML代码 var item="<div>"+m.from+"对"+m.to+"说:"+m.message+"</div>"; //拼成一个大的HTML代码放在下面,字符串代码直接加就行 finalHtml+=item; } //放进去 $(".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 success:function (result){ //如果后端发送的值是JSON,那么前端是可以直接当作对象来用的 // //打印日志观察两者类型的差异 // console.log(result); // console.log(result.ok); var jsonObj=JSON.parse(result);//JSON转对象 if(jsonObj.ok==1){ //成功 //2. 构造节点 var divE = "<div>"+from +"对" + to + "说:" + say+"</div>"; //3. 把节点添加到页面上 $(".container").append(divE);//追加在后面 //4. 清空输入框的值 $('#from').val(""); $('#to').val(""); $('#say').val(""); }else{ //失败 alert("留言发布失败"); } } }); } </script> </body> </html>
3.3.5 运行测试
此时在浏览器通过 URL http://127.0.0.1:8080/messagewall.html 访问服务器, 即可看到
留言板前后端交互演示
此时我们每次提交的数据都会发送给服务器. 每次打开页面的时候页面都会从服务器加载数据. 因此即使关闭页面, 数据也不会丢失.
但是数据此时是存储在服务器的内存中 ( private List<Message> messages = new
ArrayList<Message>(); ), 一旦服务器重启, 数据仍然会丢失.
要想数据不丢失, 需要把数据存储在数据库中, 后面再讲~~
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/Miraitowa_6/article/details/168342633




























































