Spring - 3 ( 12000 字 Spring 入门级教程 )

发布于:2024-04-28 ⋅ 阅读:(29) ⋅ 点赞:(0)

一:Spring Web MVC入门

1.1 响应

在我们前⾯的代码例子中,都已经设置了响应数据, Http 响应结果可以是数据, 也可以是静态页面,也可以针对响应设置状态码, Header 信息等.

1.2 返回静态页面

创建前端页面 index.html(注意路径)

在这里插入图片描述

  1. html代码
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Index⻚⾯</title>
</head>

<body>
Hello,Spring MVC,我是Index⻚⾯.
</body>

</html>
  1. 后台代码:
@RestController
public class IndexController {
    @RequestMapping("/index")
    public Object index(){
		//返回index.html
        return "/index.html";
    }
}

运行结果:http://127.0.0.1:8080/index

在这里插入图片描述
结果却发现, 页面未正确返回, http响应把 “/index.html” 当做了http响应正文的数据,那 Spring MVC 如何才能识别出来 index.html 是⼀个静态页面, 并进行返回呢?

我们需要把 @RestController 改为 @Controller

正确代码如下:

@Controller
public class IndexController {
    @RequestMapping("/index")
    public Object index(){
        return "/index.html";
    }
}

再次运行: http://127.0.0.1:8080/index

在这里插入图片描述
发现页面正确展示了

@RestController 和 @Controller 有着什么样的关联和区别呢,咱们前⾯讲了MVC模式, 后端会返回视图, 这是早期时的概念

在这里插入图片描述
随着互联网的发展, 目前项目开发流行"前后端分离"模式, Java 主要是用来做后端项目的开发, 所以也就不再处理前端相关的内容了

因此 MVC 的概念也逐渐发生了变化, View 不再返回视图, 而是返回显示视图时需要的数据.所以前⾯使⽤的 @RestController 其实是返回的数据.

其实 @RestController = @Controller + @ResponseBody

  • @Controller : 定义⼀个控制器, Spring 框架启动时加载, 把这个对象交给Spring管理.
  • @ResponseBody : 定义返回的数据格式为非视图, 返回⼀个 text/html 信息

所以如果想返回视图的话, 只需要用 @Controller 就可以了

1.3 返回数据@ResponseBody

我们上⾯讲到, @ResponseBody 表示返回数据.

@Controller
@ResponseBody
public class IndexController {
    @RequestMapping("/index")
    public Object index(){
        return "/index.html";
    }
}

加上 @ResponseBody 注解, 该方法就会把 “/index.html” 当做⼀个数据返回给前端.

运行:http://127.0.0.1:8080/index

在这里插入图片描述
@ResponseBody 既是类注解, ⼜是方法注解

  • 如果作用在类上, 表示该类的所有方法, 返回的都是数据,
  • 如果作用在方法上, 表示该方法返回的是数据.

也就是说: 在类上添加 @ResponseBody 就相当于在所有的方法上添加了 @ResponseBody 注解.同样, 如果类上有 @RestController 注解时:表示所有的方法上添加了 @ResponseBody 注解

如果⼀个类的方法里, 既有返回数据的, 又有返回页面的, 就把 @ResponseBody 注解添加到需要返回页面的方法上即可.

@Controller
public class IndexController {
    @RequestMapping("/index")
    public Object index(){
        return "/index.html";
    }
    @RequestMapping("/returnData")
    @ResponseBody
    public String returnData(){
        return "该⽅法返回数据";
    }
}

多个注解时, 没有先后顺序, 先写哪个都可以

运行程序, 浏览器响应结果如下: http://127.0.0.1:8080/returnData

在这里插入图片描述
如果去掉 @ResponseBody 注解, 程序会报404错误,程序会认为需要返回的是视图, 根据内容去查找文件, 但是查询不到, 路径不存在, 报404

在这里插入图片描述

1.4 返回HTML代码片段

后端返回数据时, 如果数据中有 HTML 代码, 也会被浏览器解析

@RequestMapping("/returnHtml")
@ResponseBody
public String returnHtml() {
        return "<h1>Hello,HTML~</h1>";
}

运行程序, 浏览器响应结果如下: http://127.0.0.1:8080/returnHtml

在这里插入图片描述
通过 Fiddler 观察响应结果, Content-Type 为 text/html

在这里插入图片描述
响应中的 Content-Type 常见取值有以下几种:

  • text/html : body 数据格式是 HTML
  • text/css : body 数据格式是 CSS
  • application/javascript : body 数据格式是 JavaScript
  • application/json : body 数据格式是 JSON

1.5 返回JSON

Spring MVC 也可以返回 JSON

后端方法返回结果为对象:

@RequestMapping("/returnJson")
@ResponseBody
public HashMap<String, String> returnJson() {
        HashMap<String, String> map = new HashMap<>();
        map.put("Java", "Java Value");
        map.put("MySQL", "MySQL Value");
        map.put("Redis", "Redis Value");
        return map;
}

运行程序, 浏览器响应结果如下: http://127.0.0.1:8080/returnJson

在这里插入图片描述
过 Fiddler 观察响应结果, Content-Type 为 application/json

在这里插入图片描述

1.6 设置状态码

Spring MVC 会根据我们方法的返回结果自动设置响应状态码, 程序员也可以手动指定状态码,状态码可以通过 Spring MVC 的内置对象 HttpServletResponse 提供的方法来进行设置

@RequestMapping(value = "/setStatus")
@ResponseBody
public String setStatus(HttpServletResponse response) {
        response.setStatus(401);
        return "设置状态码成功";
}

运行程序, 浏览器响应结果如下: http://127.0.0.1:8080/setStatus

在这里插入图片描述
通过 Fiddler 来观察设置的结果:

在这里插入图片描述

1.7 设置Header (了解)

Http 响应报头也会向客户端传递⼀些附加信息,这些信息通过 @RequestMapping 注解的属性来实现

先来看 @RequestMapping 的源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    String name() default "";
    @AliasFor("path")
    String[] value() default {};
    @AliasFor("value")
    String[] path() default {};
    RequestMethod[] method() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
}
  1. value: 指定映射的URL
  2. method: 指定请求的 method 类型, 如GET, POST等
  3. consumes: 指定处理请求的 Content-Type,例如 application/json,text/html;
  4. produces: 指定返回的内容类型,仅当 request 请求头中的 Accept 类型中包含该指定类型才返回
  5. Params: 指定 request 中必须包含某些参数值时,才让该方法处理
  6. headers: 指定 request 中必须包含某些指定的 header 值,才能让该方法处理请求

1.8 设置 Content-Type

我们通过设置 produces 属性的值, 设置响应的报头 Content-Type

@RequestMapping(value = "/returnJson2",produces = "application/json")
@ResponseBody
public String returnJson2() {
        return "{\"success\":true}";
}

运行程序, 浏览器响应结果如下:http://127.0.0.1:8080/returnJson2

在这里插入图片描述
通过 Fiddler 来观察设置的结果:

在这里插入图片描述
如果不设置 produces , 方法返回结果为 String 时, Spring MVC 默认返回类型是 text/html.

1.9 设置其他Header

设置其他 Header 的话, 需要使⽤ Spring MVC 的内置对象 HttpServletResponse 提供的方法来进行设置

@RequestMapping(value = "/setHeader")
@ResponseBody
public String setHeader(HttpServletResponse response) {
        response.setHeader("MyHeader","MyHeaderValue");
        return "设置Header成功";
}

void setHeader(String name, String value) 设置⼀个带有给定的名称和值的 header. 如果 name已经存在, 则覆盖旧的值.

运行程序, 浏览器响应结果如下: http://127.0.0.1:8080/setHeader

在这里插入图片描述
通过 Fiddler 来观察设置的结果:
在这里插入图片描述

二:综合性练习

结合上述内容, 我们可以做⼀些小案例,主要掌握知识点:

  1. 理解前后端交互过程
  2. 接口传参, 数据返回, 以及页面展示

2.1 加法计算器

需求: 输入两个整数, 点击 “点击相加” 按钮, 显示计算结果

在这里插入图片描述

  1. 创建 SpringBoot 项目: 引⼊ Spring Web 依赖, 把前端页面放在项目中

在这里插入图片描述

  1. 约定前后端交互接口:

首先进行需求分析:加法计算器功能, 对两个整数进行相加, 需要客户端提供参与计算的两个数, 服务端返回这两个整数计算的结果,基于以上分析, 我们来定义接口:

接口定义:

请求路径:calc/sum
请求⽅式:GET/POST
接⼝描述:计算两个整数相加

请求参数:

参数名 类型 是否必须 备注
num1 Integer 参与计算的第⼀个数
num2 Integer 参与计算的第⼆个数

响应数据:

Content-Type: text/html
响应内容: 计算机计算结果: 8

2.1.1 服务器代码

@RestController
@RequestMapping("/calc")
public class CalcController {
    @RequestMapping("/sum")
    public String sum(Integer num1,Integer num2){
        Integer sum = num1+num2;
        return "<h1>计算机计算结果: "+sum+"</h1>";
    }
}

2.1.2 调整前端页面代码

<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>

2.1.3 运行测试

启动服务, 运行并测试
在这里插入图片描述

2.2 图书管理系统

需求:

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

在这里插入图片描述
在这里插入图片描述

  1. 准备工作

创建新项目, 引入对应依赖, 把前端页面放在项目中

  1. 约定前后端交互接口

需求分析:图书管理系统是⼀个相对较大一点的案例, 咱们先实现其中的⼀部分功能.

  • 用户登录

在这里插入图片描述

  • 图书列表

在这里插入图片描述

根据需求可以得知, 后端需要提供两个接口

  1. 账号密码校验接口: 根据输入用户名和密码校验登录是否通过
  2. 图书列表: 提供图书列表信息

接口定义:

  1. 登录接⼝
[URL]
POST /user/login
[请求参数]
name=admin&password=admin
[响应]
true //账号密码验证成功
false//账号密码验证失败
  1. 图书列表展示
[URL]
POST /book/getList
[请求参数][响应]
返回图书列表
[
{
"id": 1,
"bookName": "活着",
"author": "余华",
"count": 270,
"price": 20,
"publish": "北京⽂艺出版社",
"status": 1,
"statusCN": "可借阅"
},
...
]
  1. 字段说明:
参数名 类型 是否必须 备注
id Integer 图书ID
bookName String 图书名称
author String 作者
count Integer 数量
price Double 定价
publish String 图书出版社
status Integer 图书状态
statusCN String 图书状态中文含义

2.2.1 服务器代码

  1. 创建图书类 BookInfo
@Data
public class BookInfo {
    //图书ID
    private Integer id;
    //书名
    private String bookName;
    //作者
    private String author;
    //数量
    private Integer count;
    //定价
    private BigDecimal price;
    //出版社
    private String publish;
    //状态 0-⽆效 1-允许借阅   2-不允许借阅
    private Integer status;
    private String statusCN;
    //创建时间
    private Date createTime;
    //更新时间
    private Date updateTime;
}
  1. 创建 UserController, 实现登录验证接口
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RequestMapping("/user")
@RestController
public class UserController {
    @RequestMapping("/login")
    public boolean login(String name, String password, HttpSession session){
		//账号或密码为空
        if (!StringUtils.hasLength(name) || !StringUtils.hasLength(password)){
            return false;
        }
		//模拟验证数据, 账号密码正确
        if("admin".equals(name) && "admin".equals(password)){
            session.setAttribute("userName",name);
            return true;
        }
		//账号密码错误
        return false;
    }
}
  1. 创建 BookController, 获取图书列表
@RequestMapping("/book")
@RestController
public class BookController {
    @RequestMapping("/getList")
    public List<BookInfo> getList(){
		//获取数据
        List<BookInfo> books = mockData();
		//处理⻚⾯展⽰
        for (BookInfo book:books){
            if (book.getStatus()==1){
                book.setStatusCN("可借阅");
            }else {
                book.setStatusCN("不可借阅");
            }
        }
        return books;
    }
		/**
		 * 数据Mock 获取图书信息
		 *
		 * @return
		 */
public List<BookInfo> mockData() {
    List<BookInfo> books = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        BookInfo book = new BookInfo();
        book.setId(i);
        book.setBookName("书籍" + i);
        book.setAuthor("作者" + i);
        book.setCount(i * 5 + 3);
        book.setPrice(new BigDecimal(new Random().nextInt(100)));
        book.setPublish("出版社" + i);
        book.setStatus(1);
        books.add(book);
    }
    return books;
}

}

在这个实例中数据采⽤ mock(假数据) 的方式, 实际数据应该从数据库中获取

2.2.2 调整前端页面代码

  1. 登录页面:

添加登录处理逻辑

<script src="js/jquery.min.js"></script>
<script>
    function login() {
        $.ajax({
            type: "post",
            url: "/user/login",
            data: {
                name: $("#userName").val(),
                password: $("#password").val()
            },
            success: function (result) {
                if (result) {
                    location.href = "book_list.html";
                } else {
                    alert("账号或密码不正确!");
                }
            }
        });
    }
</script>
  1. 图书列表展示:

删除前端伪造的代码, 从后端获取数据并渲染到页面上

function getBookList() {
    $.ajax({
        type: "get",
        url: "/book/getList",
        success: function(result) {
            console.log(result);
            if (result != null) {
                var finalHtml = "";
                for (var book of result) {
                    finalHtml += '<tr>';
                    finalHtml += '<td><input type="checkbox" name="selectBook"></td>';
                    finalHtml += '<td>' + book.id + '</td>';
                    finalHtml += '<td>' + book.bookName + '</td>';
                    finalHtml += '<td>' + book.author + '</td>';
                    finalHtml += '<td>' + book.count + '</td>';
                    finalHtml += '<td>' + book.price + '</td>';
                    finalHtml += '<td>' + book.publish + '</td>';
                    finalHtml += '<td>' + book.statusCN + '</td>';
                    finalHtml += '<td><div class="op">';
                    finalHtml += '<a href="book_update.html?bookId=' + book.id + '">Update</a>';
                    finalHtml += '<a href="javascript:void(0)" οnclick="deleteBook(' + book.id + ')">Delete</a>';
                    finalHtml += '</div></td>';
                    finalHtml += '</tr>';
                }
                $("tbody").html(finalHtml);
            }
        }
    });
}

2.2.3 运行测试

访问: http://127.0.0.1:8080/login.html

输入账号密码: admin admin, 登录成功, 跳转到图书列表页,界⾯展示:

在这里插入图片描述

三:lombok

3.1 lombok介绍

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

下面来简单来学习下它的使用:

  1. 引入依赖
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>
  1. 使用

lombok通过⼀些注解的方式, 可以帮助我们消除⼀些冗长代码, 使代码看起来简洁⼀些,比如之前的 Person 对象 就可以改为

@Data
public class Person {
    private int id;
    private String name;
    private String password;
}

@Data 注解会帮助我们⾃动⼀些方法, 包含 getter/setter, equals, toString 等

  1. 原理解释

加了 @Data 注解之后, Idea 反编译的 class ⽂件,不是真正的字节码文件,而是 Idea 根据字节码进行反编译后的文件

反编译是将可执行的程序代码转换为某种形式的高级编程语言, 使其具有更易读的格式. 反编译是⼀种逆向工程,它的作用与编译器的作用相反

在这里插入图片描述
在这里插入图片描述
可以看出来, lombok 是⼀款在编译期生成代码的工具包.

  • Java 程序的运行原理:

在这里插入图片描述

  • Lombok 的作用如下图所示:

在这里插入图片描述
4. 更多使用

如果觉得 @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

3.2 更快捷的引入依赖

上述引入 lombok 依赖, 需要去找 lombok 的坐标,接下来介绍更简单引入依赖的⽅式

  1. 安装插件 EditStarter, 重启 Idea

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

注意:不是所有依赖都可以在这里添加的, 这个界面和 SpringBoot 创建项目界⾯⼀样,依赖不在这里的, 还需要去 Maven 仓库查找坐标, 添加依赖.

四: 应用分层

通过上面的练习, 我们学习了 Spring MVC 简单功能的开发, 但是我们也发现了⼀些问题:目前我们程序的代码有点 “杂乱”, 然而当前只是 “⼀点点功能” 的开发. 如果我们把整个项目功能完成呢?代码会更加的 “杂乱无章”

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

在这里插入图片描述

阿里开发手册中, 关于工程结构部分, 定义了常见工程的应用分层结构:

在这里插入图片描述

  • 那么什么是应用分层呢?

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

  • 为什么需要应用分层?

在最开始的时候,为了让项目快速上线,我们通常是不考虑分层的.

但是随着业务越来越复杂,大量的代码混在⼀起,会出现逻辑不清晰、各模块相互依赖、代码扩展性差、改动⼀处就牵⼀发而动全身等问题. 所以我们需要对应用进行分层

  • 如何分层(三层架构)

咱们上⼀节中学习的 MVC, 就是把整体的系统分成了 Model(模型), View(视图)和Controller(控制器)三个层次

也就是将用户视图和业务处理隔离开,并且通过控制器连接起来,很好地实现了表现和逻辑的解耦,是⼀种标准的软件分层架构。

在这里插入图片描述

目前现在更主流的开发方式是 “前后端分离” 的方式, 后端开发工程师不再需要关注前端的实现,

所以对于Java后端开发者, ⼜有了⼀种新的分层架构: 把整体架构分为表现层、业务逻辑层和数据层. 这种分层⽅式也称之为"三层架构".

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

可以看到, 咱们前面的代码, 并不符合这种设计思想, 我们将所有的代码堆砌在⼀起

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

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

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

在这里插入图片描述

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

4.1 MVC 和三层架构的区别和联系

Spring MVC 是针对 Web 应用程序的一个具体框架,而三层架构是一种通用的软件架构模式,Spring MVC 是在表示层的基础上实现了 MVC 设计模式,而三层架构涵盖了整个应用程序,包括表示层、业务逻辑层和数据访问层。

在这里插入图片描述

4.2 代码重构

我们使用上⾯的分层思想, 来对代码进行改造

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

在这里插入图片描述

  1. 控制层: 接收前端发送的请求,对请求进行处理,并响应数据
package com.example.demo.controller;
import com.example.demo.model.BookInfo;
import com.example.demo.service.BookService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RequestMapping("/book")
@RestController
public class BookController {
    @RequestMapping("/getList")
    public List<BookInfo> getList(){
        BookService bookService = new BookService();
		//获取数据
        List<BookInfo> books = bookService.getBookList();
        return books;
    }
}
  1. 业务逻辑层: 处理具体的业务逻辑。
import com.example.demo.dao.BookDao;
import com.example.demo.model.BookInfo;
import java.util.List;
public class BookService {
    public List<BookInfo> getBookList(){
        BookDao bookDao = new BookDao();
        List<BookInfo> books = bookDao.mockData();
        for (BookInfo book:books){
            if (book.getStatus()==1){
                book.setStatusCN("可借阅");
            }else {
                book.setStatusCN("不可借阅");
            }
        }
        return books;
    }
}
  1. 数据访问层: 负责数据访问操作,包括数据的增、删、改、查
import java.util.List;
import java.util.Random;
public class BookDao {
    /**
     * 数据Mock 获取图书信息
     * @return
     */
    public List<BookInfo> mockData(){
        List<BookInfo> books = new ArrayList<>();
        for (int i=0;i<5;i++){
            BookInfo book = new BookInfo();
            book.setId(i);
            book.setBookName("书籍"+i);
            book.setAuthor("作者"+i);
            book.setCount(i*5+3);
            book.setPrice(new BigDecimal(new Random().nextInt(100)));
            book.setPublish("出版社"+i);
            book.setStatus(1);
            books.add(book);
        }
        return books;
    }
}

4.3 应用分层的好处

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

4.4 5. 企业规范

  1. 类名使用大驼峰风格,但以下情形例外:DO/BO/DTO/VO/AO
  2. 方法名、参数名、成员变量、局部变量统⼀使用小驼峰风格
  3. 包名统⼀使⽤小写,点分隔符之间有且仅有⼀个自然语义的英语单词.

常见命名命名风格介绍

  • 大驼峰: 所有单词首字⺟都需要大写, 又叫帕斯卡命名法, 比如: UserController
  • 小驼峰: 除了第⼀个单词,其他单词首字⺟大写,比如: userController
  • 蛇形: 用下划线(_)作⽤单词间的分隔符, ⼀般小写, 又叫下划线命名法, 比如: user_controller
  • 串形: 用短横线(-)作⽤单词间的分隔符, ⼜叫脊柱命名法,比如: user-controller

网站公告

今日签到

点亮在社区的每一天
去签到