首页 / 资讯中心 / 文章详情

Java EE 进阶:4.Spring Web MVC 入门(第五弹)

Java EE 进阶:4.Spring Web MVC 入门(第五弹) ★ FEATURED ARTICLE
目录3.3 留言板3.3.1 准备工作3.3.2 约定前后端交互接口需求分析接口定义1.获取全部留言2.发表新留言3.3.3 实现服务器端代码3.3.3.1 lombok 介绍重点1.引入依赖2.使用3.原理解释4.更多使用3.3.3.2 更快捷的引入依赖1.安装插件 EditStarter重启 IDEA​编辑​编辑2.在pom.xml 文件中单击右键选择 Generate操作如下图所示3.3.3.3 服务器代码实现3.3.4 调整前端页面代码1.在点击按钮的时候给服务器发送添加留言请求2.添加 load 函数用于在页面加载的时候获取数据3.3.5 运行测试书接上文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 groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency直接复制到 xml 文件里点击刷新后会自动为我们导入依赖当然如果你不知道这个依赖是什么咱就可以去 Maven 中央仓库去查询https://mvnrepository.com/search?qlombok第一个就是了然后随便找一个使用次数多的版本有漏洞的不要选尽量选次新的点进去复制 依赖 即可粘贴到我们的 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 / setterequalstoString 等此时我们测试一下发现自动就出来了 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属性不能为 nullRequiredArgsConstructor自动添加必需属性的构造方法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 类使用ListMessageInfo 来存储留言板信息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 ListMessageInfo messageInfoListnew 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 ListMessageInfo 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.html1.在点击按钮的时候给服务器发送添加留言请求!DOCTYPE html html langen head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.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 classcontainer h1留言板/h1 p classgrey输入后点击提交, 会将信息显示下方空白处/p div classrow span谁:/span input typetext name idfrom /div div classrow span对谁:/span input typetext name idto /div div classrow span说什么:/span input typetext name idsay /div input typebutton value提交 idsubmit onclicksubmit() !-- divA 对 B 说: hello/div -- /div script srchttps://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.ok1){ //成功 //2. 构造节点 var divE divfrom 对 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那如果把后端的 producesapplication/json 去掉会怎么样我们当时为了改成 JSON通过这种方式手动给改了那如果不改那么返回来的就是一个字符串咱们再来测试我们发现 Content-Type 是文本类型那么文本类型为什么留言失败了我们来分析一下success:function (result){ if(result.ok1){ //成功 //2. 构造节点 var divE divfrom 对 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.ok1){ //成功 //2. 构造节点 var divE divfrom 对 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 jsonObjJSON.parse(result);//JSON转对象 if(jsonObj.ok1){ //成功 //2. 构造节点 var divE divfrom 对 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!nullmessages.length0){ var finalHtml; //类似Java的for(MessageInfo messageInfo:messageInfoList) for(var m of messages){ //给每一个元素都拼接上这样一行HTML代码 var itemdivm.from对m.to说:m.message/div; //拼成一个大的HTML代码放在下面字符串代码直接加就行 finalHtmlitem; } //放进去 $(.container).append(finalHtml); } } }); }完整代码!DOCTYPE html html langen head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.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 classcontainer h1留言板/h1 p classgrey输入后点击提交, 会将信息显示下方空白处/p div classrow span谁:/span input typetext name idfrom /div div classrow span对谁:/span input typetext name idto /div div classrow span说什么:/span input typetext name idsay /div input typebutton value提交 idsubmit onclicksubmit() !-- divA 对 B 说: hello/div -- /div script srchttps://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!nullmessages.length0){ var finalHtml; //类似Java的for(MessageInfo messageInfo:messageInfoList) for(var m of messages){ //给每一个元素都拼接上这样一行HTML代码 var itemdivm.from对m.to说:m.message/div; //拼成一个大的HTML代码放在下面字符串代码直接加就行 finalHtmlitem; } //放进去 $(.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 jsonObjJSON.parse(result);//JSON转对象 if(jsonObj.ok1){ //成功 //2. 构造节点 var divE divfrom 对 to 说: say/div; //3. 把节点添加到页面上 $(.container).append(divE);//追加在后面 //4. 清空输入框的值 $(#from).val(); $(#to).val(); $(#say).val(); }else{ //失败 alert(留言发布失败); } } }); } /script /body /html3.3.5 运行测试此时在浏览器通过 URL http://127.0.0.1:8080/messagewall.html 访问服务器, 即可看到留言板前后端交互演示此时我们每次提交的数据都会发送给服务器. 每次打开页面的时候页面都会从服务器加载数据. 因此即使关闭页面, 数据也不会丢失.但是数据此时是存储在服务器的内存中 ( private ListMessage messages newArrayListMessage(); ), 一旦服务器重启, 数据仍然会丢失.要想数据不丢失, 需要把数据存储在数据库中, 后面再讲~~
阅读完成 · 觉得有帮助?
咨询建站