【SpringBoot】从零开始全面解析SpringMVC (三)

本篇博客给大家带来的是SpringBoot的知识点, 本篇是SpringBoot入门, 介绍SpringMVC相关知识.
🐎文章专栏: JavaEE进阶
🚀若有问题 评论区见
❤ 欢迎大家点赞 评论 收藏 分享
如果你不知道分享给谁,那就分享给薯条.
你们的支持是我不断创作的动力 .

王子,公主请阅🚀

  • 要开心
  • [1. 综合练习](#1. 综合练习)
    • [1.1 留言板](#1.1 留言板)
      • [1.1.1 准备工作](#1.1.1 准备工作)
      • [1.1.2 约定前后端交互接口](#1.1.2 约定前后端交互接口)
      • [1.1.3 实现服务器代码:](#1.1.3 实现服务器代码:)
    • [1.2 图书管理系统](#1.2 图书管理系统)
      • [1.2.1 准备工作](#1.2.1 准备工作)
    • [1.2.2 约定前后端交互接口](#1.2.2 约定前后端交互接口)
  • [2. 应用分层](#2. 应用分层)
    • [2.1 分层结构介绍](#2.1 分层结构介绍)
    • [2.2 代码重构](#2.2 代码重构)
    • [2.3 应用分层的好处](#2.3 应用分层的好处)
  • [3. 总结](#3. 总结)
    • [3.1 注解](#3.1 注解)
    • [3.2 Cookie 和 Session](#3.2 Cookie 和 Session)

要开心

要快乐

顺便进步

1. 综合练习

1.1 留言板

需求:
界面如上图所示👆

  1. 输入留言信息, 点击提交. 后端把数据存储起来.
  2. 页面展示输入得表白墙的信息.

1.1.1 准备工作

码云链接: 薯条不要番茄酱

1.1.2 约定前后端交互接口

Ⅰ 发布留言
url : /message/publish .
param(参数) : from,to,say .
return : true / false .

Ⅱ 查询留言
url : /message/getList.
param : 无
return : form 对 to 说了 say

1.1.3 实现服务器代码:

Ⅰ 写代码之前,介绍一个新的工具包 lombok.

Lombok是一个Java工具库,通过添加注解的方式简化Java的开发.

① 引入依赖

java 复制代码
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

② 使用

lombok 通过一些注解的方式, 可以帮助我们消除一些冗长代码, 使代码看起来简洁一些. 就比如: 上面写的 Student 对象, 无需自己写 get , set , toString方法. 只需写一个注解 @Data就可以了.

java 复制代码
import lombok.Data;

@Data
public class Student {
    private String name;
    private Integer id;
    private int age;
}

③ 原理解释

加了 @Data 注解之后, Idea 反编译的class文件.

这不是真正的字节码文件, 而是Idea根据字节码进行反编译后的文件.
反编译是将可执行的程序代码转换为某种形式的高级编程语言, 使其具有更易读的格式. 反编译是一种逆向工程,它的作用与编译器的作用相反.

④ 更多使用

如果觉得 @Data生成的方法太多, lombok 也提供了一些更精细粒度的注解.

Ⅱ 更快捷引入依赖

① 安装插件EditStarter, 重启Idea.

② 在pom.xml 文件中, 单击右键, 选择Generate, 操作如下图所示

③ 进入 Edit Starters的编辑界面, 添加对应依赖即可.

Ⅲ 实现服务器代码

定义留言对象

java 复制代码
import lombok.Data;

@Data
public class MessageInfo {
    private String from;
    private String to;
    private String say;
}

创建 MessageController 类

java 复制代码
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RequestMapping("/message")
@RestController
public class MessageController {
    List<MessageInfo> messageInfos = new ArrayList<>();

    @RequestMapping("/publish")
    public Boolean publish(MessageInfo messageInfo) {
        //校验信息
        if(!StringUtils.hasLength(messageInfo.getFrom())
                || !StringUtils.hasLength(messageInfo.getTo())
                || !StringUtils.hasLength(messageInfo.getSay())) {
            return false;
        }
        //把信息存起来方便下一个方法获取
        messageInfos.add(messageInfo);
        return true;
    }
    @RequestMapping("/getList")
    public List<MessageInfo> getList() {
        return messageInfos;
    }
}

前端页面代码

java 复制代码
<!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() {
            $.ajax({
                url: "/message/publish",
                type: "get",
                data: {
                    from: $("#from").val(),
                    to: $("#to").val(),
                    say: $("#say").val()
                },
                //http响应成功
                success:function(result) {
                    if(result == false) {
                        alert("输入不合法");
                    }else {
                        //展示信息
                        //1. 构造节点
                        var divE = "<div>"+from+ "对" + to + "说:" + say + "</div>";
                        //2. 把节点添加到页面上
                        $(".container").append(divE);

                        //3. 清空输入框的值
                        $("#from").val("");
                        $("#to").val("");
                        $("#say").val("");
                    }
                }
            });
        }
    </script>
</body>

</html>

1.2 图书管理系统

需求:

  1. 登录: 用户输入账号,密码完成登录功能.
  2. 列表展示: 展示图书.


1.2.1 准备工作

创建新项目,引入依赖.

将前端代码复制到 static 目录下.

1.2.2 约定前后端交互接口

先实现其中的两个功能: 用户登录 和 图书列表展示.

需求分析:

  1. 用户登录
    url : /user/login
    param : userName 和 password
    return : String(提示)

  2. 图书列表展示 1. url : /book/getBookList

  3. param : 无

  4. return : 图书列表

1.2.3 服务器代码

创建图书类

java 复制代码
import lombok.Data;

import java.math.BigDecimal;

@Data
public class BookInfo {
    private Integer id;
    private String bookName;
    private String author;
    private Integer num;
    private BigDecimal price;
    private String publishName;
    private Integer status;//对于可枚举的属性(以后可能会改的属性),通常用数字表示: 1-可借阅, 2-不可借阅
    private String statusCN;
}

创建UserController, 实现登录验证接口.

java 复制代码
import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController {
    @RequestMapping("/login")
    public String  login(String userName, String password, HttpSession session) {
        //校验用户信息是否合法.
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
            return "用户名或密码为空";
        }
        //判断用户名和密码是否正确
        //理论上应该从数据库中获取, 但是目前还没学习 mybatis, 所以先这么写.
        if(!"admin".equals(userName) || !"admin".equals(password)) {
            return "用户名或密码错误";
        }
        session.setAttribute("userName",userName);
        return "";
    }
}

创建BookController, 获取图书列表

java 复制代码
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

@RequestMapping("/book")
@RestController
public class BookController {
    @RequestMapping("/getBookList")
    public List<BookInfo> getBookList() {
        List<BookInfo> bookInfos = new ArrayList<>();
        //mock(模拟) 数据
        for (int i = 1; i <= 15; i++) {
            BookInfo bookInfo = new BookInfo();
            bookInfo.setId(i);
            bookInfo.setBookName("图书"+i);
            bookInfo.setAuthor("作者"+i);
            bookInfo.setNum(i*2+1);
            bookInfo.setPrice(new BigDecimal(i*3));
            bookInfo.setPublishName("出版社"+i);
            if(i % 5 == 0) {
                bookInfo.setStatus(2);
                bookInfo.setStatusCN("不可借阅");
            }else {
                bookInfo.setStatus(1);
                bookInfo.setStatusCN("可借阅");
            }
            bookInfos.add(bookInfo);
        }
        return bookInfos;
    }
}

调整前端页面代码

登录页面:

java 复制代码
<!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({
                url : "/user/login",
                type : "post",
                data : {
                    userName : $("#userName").val(),
                    password : $("#password").val(),
                },
                success:function(result) {
                    if(result != "") {
                        alert("用户名或密码错误,请重新输入");
                    }else {
                        location.href = "book_list.html";
                    }
                }
            });
        }
    </script>
</body>

</html>

图书列表展示:

java 复制代码
<!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>
                
            </tbody>
        </table>

        <div class="demo">
            <ul id="pageContainer" class="pagination justify-content-center"></ul>
        </div>
        <script>

            getBookList();
            function getBookList() {
                $.ajax({
                    url:"/book/getBookList",
                    type:"get",
                    success:function(books) {
                        var finnalHtml = "";
                        for(var book of books) {
                            finnalHtml += '<tr>';
                            finnalHtml += '<td><input type="checkbox" name="selectBook" value="1" id="selectBook" class="book-select"></td>';
                            finnalHtml += '<td>'+book.id+'</td>';
                            finnalHtml += '<td>'+book.bookName+'</td>';
                            finnalHtml += '<td>'+book.author+'</td>';
                            finnalHtml += '<td>'+book.num+'</td>';
                            finnalHtml += '<td>'+book.price+'</td>';
                            finnalHtml += '<td>'+book.publishName+'</td>';
                            finnalHtml += '<td>'+book.statusCN+'</td>';
                            finnalHtml += '<td>';
                            finnalHtml += '<div class="op">';
                            finnalHtml += '<a href="book_update.html?bookId='+book.id+'">修改</a>';
                            finnalHtml += '<a href="javascript:void(0)" onclick="deleteBook('+book.id+')">删除</a>';
                            finnalHtml += '</div>';
                            finnalHtml += '</td>';
                            finnalHtml += '</tr>';
                        }
                        $("tbody").html(finnalHtml);
                    }
                });
            }
            //翻页信息
            $("#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>

2. 应用分层

通过上面的练习, 我们学习了Spring MVC简单功能的开发, 但是我们也发现了一些问题。
目前我们程序的代码有点"杂乱", 然而当前只是"一点点功能"的开发. 如果我们把整个项目功能完成代码会更加的"杂乱无章"(文件乱, 代码内容也乱)。

基于此, 咱们接下来学习应用分层.

2.1 分层结构介绍

Ⅰ 什么是应用分层?

应用分层 是一种软件开发设计思想, 它将应用程序分成N个层次, 这N个层次分别负责各自的职责, 多个层次之间协同提供完整的功能. 根据项目的复杂度, 把项目分成三层, 四层或者更多层.
常见的MVC设计模式, 就是应用分层的一种具体体现.

Ⅱ 应用分层的目的

在最开始的时候,为了让项目快速上线,我们通常是不考虑分层的. 但是随着业务越来越复杂,大量的
代码混在一起,会出现逻辑不清晰、各模块相互依赖、代码扩展性差、改动一处就牵动全局的问题。 所以学习对项进行分层就是我们程序员的必修课了.

Ⅲ 如何分层

"MVC" 就是把整体的系统分成了 Model(模型), View(视图)和 Controller(控制器)三个层次,也就是将用户视图和业务处理隔开,并且通过控制器连接起来,很好地实现
了表现和逻辑的解耦,是一种标准的软件分层架构。

目前更主流的开发方式是 "前后端分离" 的方式, 后端开发工程师不再需要关注前端的实现, 对于Java后端开发者, 又有了一种新的分层架构: 把整体架构分为表现层、业务逻辑层和数据层. 这种分层方式也称之为"三层架构".

  1. 表现层: 就是展示数据结果和接受用户指令的,是最靠近用户的一层;
  2. 业务逻辑层: 负责处理业务逻辑, 里面有复杂业务的具体实现;
  3. 数据层: 负责存储和管理与应用程序相关的数据。

按照上面的层次划分, Spring MVC 站在后端开发人员的角度上, 也进行了支持, 把上面的代码划分为三个部分:


请求处理、响应数据:负责,接收页面的请求,给页面响应数据.
逻辑处理: 负责业务逻辑处理的代码.
数据访问: 负责业务数据的维护操作,包括增、删、改、查等操作.

这三个部分, 在Spring的实现中, 均有体现:

Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
Service:业务逻辑层。处理具体的业务逻辑。
Dao:数据访问层,也称为持久层。负责数据访问操作,包括数据的增、删、改、查。

2.2 代码重构

使用上面的分层思想, 来对代码进行改造。

先创建对应的包路径, 并把代码移到对应的目录

com.fhao.book.controller
com.fhao.book.service
com.fhao.book.dao
com.fhao.book.model

在dao 目录下创建 BookDao文件 在service 目录下创建 BookService文件:

将 BookController 中的代码改写成👇:

java 复制代码
@RequestMapping("/getBookList")
    public List<BookInfo> getBookList() {
        BookService bookService = new BookService();
        return bookService.getBookList();
    }

BookDao文件 中的代码:

java 复制代码
public List<BookInfo> mockData() {
        //理论上该方法应该从数据库获取, 此处先mock 数据.
        List<BookInfo> bookInfos = new ArrayList<>();
        for (int i = 1; i <= 15; i++) {
            BookInfo bookInfo = new BookInfo();
            bookInfo.setId(i);
            bookInfo.setBookName("图书"+i);
            bookInfo.setAuthor("作者"+i);
            bookInfo.setNum(i*2+1);
            bookInfo.setPrice(new BigDecimal(i*3));
            bookInfo.setPublishName("出版社"+i);
            if(i % 5 == 0) {
                bookInfo.setStatus(2);
                bookInfo.setStatusCN("不可借阅");
            }else {
                bookInfo.setStatus(1);
                bookInfo.setStatusCN("可借阅");
            }
            bookInfos.add(bookInfo);
        }
        return bookInfos;
    }

BookService文件 中的代码

java 复制代码
    public List<BookInfo> getBookList() {
        BookDao bookDao = new BookDao();
        List<BookInfo> bookInfos = bookDao.mockData();
        for(BookInfo bookInfo : bookInfos) {
            if(bookInfo.getStatus() == 2) {
                bookInfo.setStatusCN("不可借阅");
            }else {
                bookInfo.setStatusCN("可借阅");
            }
        }
        return bookInfos;
    }

2.3 应用分层的好处

  1. 降低层与层之间的依赖, 结构更加的明确, 利于各层逻辑的复用.
  2. 开发人员可以只关注整个结构中的其中某一层, 极大地降低了维护成本和维护时间.
  3. 可以很容易的用新的实现来替换原有层次的实现.
  4. 有利于标准化.

3. 总结

3.1 注解

学习Spring MVC, 其实就是学习各种Web开发需要用的到注解。

a. @RequestMapping: 路由映射

b. @RequestParam: 后端参数重命名

c. @RequestBody: 接收JSON类型的参数

d. @PathVariable: 接收路径参数

e. @RequestPart: 上传文件

f. @ResponseBody: 返回数据

g. @CookieValue: 从Cookie中获取值

h. @SessionAttribute: 从Session中获取值

i. @RequestHeader: 从Header中获取值

j. @Controller: 定义一个控制器, Spring 框架启动时加载, 把这个对象交给Spring管理. 默认返回
视图.

k. @RestController: @ResponseBody + @Controller 返回数据

Cookie 和Session都是会话机制, Cookie是客户端机制, Session是服务端机制. 二者通过 SessionId来关联. Spring MVC内置 HttpServletRequest 和HttpServletResponse 两个对象. 需要使用时, 直接在方法中添加对应参数即可, Cookie 和 Session 可以从HttpServletRequest 中来获取, 也可以直接使用 HttpServletResponse 设置Http响应状态码.

本篇博客到这里就结束啦, 感谢观看 ❤❤❤
🐎期待与你的下一次相遇😊😊😊

相关推荐
exe452几秒前
力扣每日一题5-19
java·算法·leetcode
冷yan~2 分钟前
构建下一代AI智能体:基于Spring AI的多轮对话应用
java·人工智能·spring·ai
?abc!3 分钟前
spring框架中的本地缓存:spring cache基本使用
java·spring·缓存
ABCDEEE77 分钟前
SpringAI
java
不穿铠甲的穿山甲31 分钟前
mysql-Java手写分布式事物提交流程
java·分布式·mysql
嘵奇42 分钟前
Spring Boot中Redis序列化配置详解
spring boot·redis·后端
胜玲龙1 小时前
单点登录是是什么?具体流程是什么?
java·服务器·前端
快乐肚皮1 小时前
基于Spring Cloud Sentinel自研Slot扩展实战
java·spring cloud·sentinel
zc.z1 小时前
sentinel核心原理-高频问题
java·开发语言
夏季疯1 小时前
学习笔记:黑马程序员JavaWeb开发教程(2025.4.6)
java·笔记·学习