[JavaEE] Spring Web MVC入门

目录

[1. 什么是Spring Web MVC](#1. 什么是Spring Web MVC)

[2. 学习Spring MVC](#2. 学习Spring MVC)

[2.1 项目准备](#2.1 项目准备)

[2.2 建立连接](#2.2 建立连接)

[2.2.1 @RequestMapping注解介绍](#2.2.1 @RequestMapping注解介绍)

[2.3 Postman介绍](#2.3 Postman介绍)

[2.4 请求](#2.4 请求)

[2.4.1 @RequestMapping注解接收特定方法的请求](#2.4.1 @RequestMapping注解接收特定方法的请求)

[2.4.2 请求传递参数](#2.4.2 请求传递参数)

[2.4.3 请求参数传递对象](#2.4.3 请求参数传递对象)

[2.4.4 后端参数的重命名](#2.4.4 后端参数的重命名)

[2.4.5 传递数组](#2.4.5 传递数组)

[2.4.6 传递集合](#2.4.6 传递集合)

[2.4.7 传递JSON数据](#2.4.7 传递JSON数据)

[2.4.8 获取URL中参数@PathVariable](#2.4.8 获取URL中参数@PathVariable)

[2.4.9 上传文件@RequestPart](#2.4.9 上传文件@RequestPart)

[2.4.10 获取Cookie/Session](#2.4.10 获取Cookie/Session)

获取Cookie

获取Session

[2.4.11 获取Header](#2.4.11 获取Header)

[2.5 响应](#2.5 响应)

[2.5.1 返回静态页面 和 返回数据@ResponseBody](#2.5.1 返回静态页面 和 返回数据@ResponseBody)

[2.5.2 返回HTML代码片段](#2.5.2 返回HTML代码片段)

[2.5.3 返回Json](#2.5.3 返回Json)

[2.5.4 设置状态码](#2.5.4 设置状态码)

[2.5.5 设置Header](#2.5.5 设置Header)

[2.5.6 设置Content-Type](#2.5.6 设置Content-Type)

[3. 综合性练习](#3. 综合性练习)

[3.1 加法计算器](#3.1 加法计算器)

[3.1.1 约定前后端交互接口](#3.1.1 约定前后端交互接口)

[3.1.2 服务器代码](#3.1.2 服务器代码)

[3.1.3 前端代码](#3.1.3 前端代码)

[3.1.4 运行测试](#3.1.4 运行测试)

[3.2 用户登录](#3.2 用户登录)

[3.2.1 约定前后端交互接口](#3.2.1 约定前后端交互接口)

[3.2.2 服务器代码](#3.2.2 服务器代码)

[3.2.3 前端代码](#3.2.3 前端代码)

[3.2.4 运行测试](#3.2.4 运行测试)

[3.3 留言板](#3.3 留言板)

[3.3.1 约定前后端交互接口](#3.3.1 约定前后端交互接口)

[3.3.2 服务器代码](#3.3.2 服务器代码)

lombok介绍:

引入依赖的新方式:

服务器代码实现:

[3.3.3 前端代码](#3.3.3 前端代码)

[3.3.4 运行测试](#3.3.4 运行测试)

[3.4 图书管理系统](#3.4 图书管理系统)

[3.4.1 生成前端页面的工具](#3.4.1 生成前端页面的工具)

[3.4.2 约定前后端交互接口](#3.4.2 约定前后端交互接口)

[3.4.3 服务器代码](#3.4.3 服务器代码)

[3.4.4 前端代码](#3.4.4 前端代码)

[3.4.5 运行测试](#3.4.5 运行测试)

[4. 应用分层](#4. 应用分层)

[5. 总结](#5. 总结)


1. 什么是Spring Web MVC

Spring Web MVC 是基于 servlet API 构建的原始Web框架,从一开始就包含在spring框架里面,但通常称为:Spring MVC,也就是说 Spring Web MVC 最开始就包含在Spring FrameWork框架里面。

servlet:就是用来处理客户端请求,处理业务逻辑,生成响应内容的。

而Tomcat这样的servlet容器是用来接收客户端请求,管理servlet的执行,并将响应返回给客户端。

Spring Web MVC则是一个Web框架,它是基于servlet实现的一个框架,可以更加高效的处理请求。

MVC:

MVC是一种软件设计的架构模式,把软件系统分为模型,视图,控制器三层:

View(视图)指在应用程序中专门用来与浏览器进行交互,展示数据的资源.。

Model(模型)是应用程序的主体部分,用来处理程序中数据逻辑的部分。

Controller(控制器)可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型 来处理,以及处理完后需要跳回到哪一个视图。即用来连接视图和模型。

Spring MVC就是一个采用MVC架构的Web框架。

Spring Boot 项目里面包含了Spring MVC框架,使用这个框架来实现Web功能,对Spring MVC框架做出了一些修改,如图所示:

Spring Boot框架相当于集成了很多功能,类似于12306,Spring Framework相当于火车,而Spring MVC相当于售票站。

2. 学习Spring MVC

2.1 项目准备

我们学习Spring MVC 框架主要学习三个方面:

建立连接:将浏览器和服务器建立连接,使得用户能通过浏览器访问服务器的资源。

请求:用户请求时候会有一些参数,服务器能够获取到这些参数。

响应:服务器处理好请求后得到响应,然后将响应发送给浏览器。

我们创建Spring Boot项目时候,框选了Spring Web就相当于创建了Spring MVC项目:

我们需要自己在idea创建一个Spring Boot项目。

2.2 建立连接

先创建一个UserController类,来实现浏览器和服务器的连接,代码如下:

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

@RequestMapping("/user")
@RestController
public class UserController {
    @RequestMapping("/m1")
    public String m1() {
        return "m1";
    }
}

然后启动程序,访问http://127.0.0.1:8080/user/m1这个网址,就可以在网页上看到返回的数据了。

2.2.1 @RequestMapping注解介绍

这个注解相当于路径映射,可以修饰类,也可以修饰方法,URL中有资源路径,这个资源路径就是类路径+方法路径。

使用注意:

  1. 我们在使用这个注解时候,路径里面的/可以省略,Spring会帮我们检查。
  2. 注解里面可以是多层路径,第一个路径可以省略/,后面的不能省略。
  3. 类路径和方法路径可以重复,需要保证资源路径是唯一的,但是平常使用都不会重名。

这时候用户输入一个URL后,服务器能通过这个URL访问到具体的某个类某个方法,但是Spring是如果找到这个具体的类或者方法呢?

Spring通过扫描所有加了@RestController注解的类,查看里面有没有@RequestMapping注解,从而找到对应的方法,没有加@RestController注解的类不会扫描。

@RequestMapping注解既可以接收get请求又可以接收post请求。

浏览器一般发送的都是get请求,我们如何去构造一个post请求呢?

2.3 Postman介绍

我们可以使用Postman这个软件来构造不同方法的请求,测试客户端和服务器的连接,我们使用postman发送的请求给客户端 和 浏览器给客户端发送的请求是一样的效果的。

下载地址:https://www.postman.com/downloads/,可能需要注册一个账号,才能使用。

创建请求:

我们就可以使用Postman这个软件来构造请求,发送给服务器,比如我们构造一个post请求,使用fiddler抓包看是不是一个post请求:

我们使用postman构造一个post请求,发送给服务器,返回了m1。

使用fiddler抓包到请求的信息:

POST http://127.0.0.1:8080/user/m1 HTTP/1.1

User-Agent: PostmanRuntime/7.49.1

Accept: */*

Postman-Token: 47008be0-e53d-4db9-8fe4-10c06dfff6fa

Host: 127.0.0.1:8080

Accept-Encoding: gzip, deflate, br

Connection: keep-alive

Content-Length: 0

显示是一个post请求。

2.4 请求

2.4.1 @RequestMapping注解接收特定方法的请求

此时的@RuquestMapping注解既支持get请求,也支持post请求,其它请求也支持。

如果想让@RequestMapping注解只支持某一种请求,该如何去编写代码:

java 复制代码
@RequestMapping("/user")
@RestController
public class UserController {
    //get和post请求都支持
    @RequestMapping("/m1")
    public String m1() {
        return "m1";
    }
    //只支持get请请求
    @RequestMapping(value = "/m2",method = RequestMethod.GET)
    public String m2() {
        return "m2";
    }
    //只支持post请求
    @RequestMapping(value = "/m3",method = RequestMethod.POST)
    public String m3() {
        return "m3";
    }
}

这里访问http://127.0.0.1:8080/user/m2,这个路径只支持get请求,其他请求会报405错误。

访问http://127.0.0.1:8080/user/m3,这个路径只支持post请求。

上面的代码还有另外一种写法:

java 复制代码
    //只支持get请求
    @GetMapping("/m4")
    public String m4() {
        return "m4";
    }
    //只支持post请求
    @PostMapping("/m5")
    public String m5() {
        return "m5";
    }

这里的m4只支持get请求,m5只支持post请求,使用这种写法的话注解里面的字符串可以相同,这里就会根据资源路径+请求的方法来区分那个方法。

这里的method也可以支持多个请求:

java 复制代码
    //支持get和post请求
    @RequestMapping(value = "/m6",method = {RequestMethod.POST, RequestMethod.GET})
    public String m6() {
        return "m6";
    }

get和post请求都支持。

2.4.2 请求传递参数

我们使用postman构造请求时候,是可以进行传参的。

参数介绍:

Params是传普通参数,通过查询字符串的键值对的形式传递。

Headers:请求头,键值对的形式可以自己设置。

Body:正文,可以自己添加内容。

from-data是通常用于提交图片或者文件。

x-www-form-urlencoded是对应的form表单。

raw可以上传text,json,html,xml等。

我们创建一个新的类来演示RequestController类

java 复制代码
@RequestMapping("/request")
@RestController
public class RequestController {
    //传递一个参数
    @RequestMapping("/r1")
    public String r1(String keyword) {
        return "接受的参数:" + keyword;
    }
    //传递多个参数
    @RequestMapping("r2")
    public String r2(String userName, String password) {
        return "账号:" + userName + " " + "密码:" + password;
    }
}

传递一个参数的请求:

传递多个参数的请求,通过查询字符串传递数据:

传递多个请求,通过正文来传递数据:

fiddler抓包结果:

POST http://127.0.0.1:8080/request/r2 HTTP/1.1

User-Agent: PostmanRuntime/7.49.1

Accept: */*

Postman-Token: 6eabf928-ee24-493a-a2c6-bc60c40e135b

Host: 127.0.0.1:8080

Accept-Encoding: gzip, deflate, br

Connection: keep-alive

Content-Type: application/x-www-form-urlencoded

Content-Length: 30

userName=admin&password=123456

这里面的用户名和密码数据就通过正文传递了。

传递参数是整型和基本类型呢?

java 复制代码
    //传递包装类型整型
    @RequestMapping("/r3")
    public String r3(Integer number) {
        return "接收参数:" + number;
    }
    //传递基本类型整型
    @RequestMapping("/r4")
    public String r4(int number) {
        return "接收参数:" + number;
    }

传递包装类型和基本类型都能传递成功,显示200。

传递包装类型和基本类型,传递字母,会显示400,客户端错误:

如果包装类型不传参数,此时就是null,但是基本类型不传参数,就会报错,500,服务器错误,基本类型不能为null。

2.4.3 请求参数传递对象

如果传的参数过多的情况,我们接收时候可以使用一个对象来接收:

定义一个UserInfo类:

java 复制代码
package com.sias.demo.controller;

public class UserInfo {
    private String name;
    private int gender;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", gender=" + gender +
                ", age=" + age +
                '}';
    }
}

利用对象来接收请求的参数:

java 复制代码
    //使用对象来接收参数
    @RequestMapping("r5")
    public String r5(UserInfo userInfo) {
        return "接收参数userInfo = " + userInfo.toString();
    }

2.4.4 后端参数的重命名

required属性是设置该参数是否必须传参。

java 复制代码
    //接收到参数a,把a的值给keyword,使用这个注解的参数,默认是必须传参,否则报错
    @RequestMapping("r6")
    public String r6(@RequestParam("a") String keyword) {
        return "接收参数:" + keyword;
    }
    //设置非必须传参
    @RequestMapping("r7")
    public String r7(@RequestParam(value = "a", required = false) String keyword) {
        return "接收参数:" + keyword;
    }

通过postman发送请求:

强制传参:

非强制传参:

2.4.5 传递数组

java 复制代码
    //传递数组
    @RequestMapping("r8")
    public String r8(String[] arr) {
        return "接收参数:" + Arrays.toString(arr);
    }

利用postman构造请求:

方法一:

方法二:

2.4.6 传递集合

默认情况下,传递同一个参数的多个值,是会放在数组里面的,如果要存到集合里面,需要使用@RequestParam注解来绑定参数关系。

错误代码:

java 复制代码
    //传递集合
    @RequestMapping("r9")
    public String r9(List<Integer> list) {
        return "接收参数:" + list;
    }

利用上面代码接受请求,结果会报错,500:

利用@RequestParam注解绑定关系后:

java 复制代码
    //传递集合
    @RequestMapping("r9")
    public String r9(@RequestParam List<Integer> list) {
        return "接收参数:" + list;
    }

这里跟数组一样,两种传参方式都可以:

2.4.7 传递JSON数据

JSON概念:

JSON就是一种数据格式,有着自己的规则来组织数据,本质是一个字符串,是客户端和服务端进行交互的一种数据格式。

JSON语法:

  • 数据在键值对中
  • 数据之间用 , 分隔
  • 对象用 { } 表示
  • 数组用 [ ] 表示
  • 值可以为对象,也可以为数组,数组中可以包含多个对象。

示例代码:

java 复制代码
{
 "squadName": "Super hero squad",
 "homeTown": "Metro City",
 "formed": 2016,
 "secretBase": "Super tower",
 "active": true,
 "members": [{
 "name": "Molecule Man",
 "age": 29,
 "secretIdentity": "Dan Jukes",
 "powers": ["Radiation resistance", "Turning tiny", "Radiation 
blast"]
 }, {
 "name": "Madame Uppercut",
 "age": 39,
 "secretIdentity": "Jane Wilson",
 "powers": ["Million tonne punch", "Damage resistance", "Superhuman 
reflexes"]
 }, {
 "name": "Eternal Flame",
 "age": 1000000,
 "secretIdentity": "Unknown",
 "powers": ["Immortality", "Heat Immunity", "Inferno", 
"Teleportation", "Interdimensional travel"]
 }]
}

压缩版本:

{ "squadName": "Super hero squad", "homeTown": "Metro City", "formed": 2016, "secretBase": "Super tower", "active": true, "members": [{ "name": "Molecule Man", "age": 29, "secretIdentity": "Dan Jukes", "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"] }, { "name": "Madame Uppercut", "age": 39, "secretIdentity": "Jane Wilson", "powers": ["Million tonne punch", "Damage resistance", "Superhuman reflexes"] }, { "name": "Eternal Flame", "age": 1000000, "secretIdentity": "Unknown", "powers": ["Immortality", "Heat Immunity", "Inferno", "Teleportation", "Interdimensional travel"] }] }

我们可以通过一些格式化工具将上面的压缩版本格式化:

https://www.json.cn/

JSON字符串和Java对象转换:

常用的转换工具:gson,fastjson,jackson。

spring boot项目自带的是jackson,我们可以直接去使用。

我们在test目录下,创建一个JsonTest类,来演示Jackson的使用:

java 复制代码
package com.sias.demo;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sias.demo.controller.UserInfo;
import org.junit.jupiter.api.Test;

public class JsonTest {
    //对象转Json
    @Test
    void testObjectToJson() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        //构造一个对象
        UserInfo userInfo = new UserInfo();
        userInfo.setName("张三");
        userInfo.setGender(1);
        userInfo.setAge(18);
        //对象转JSON
        String s = objectMapper.writeValueAsString(userInfo);
        System.out.println(s);
    }

    //Json转对象
    @Test
    void testJsonToObject() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        //构造一个Json
        String s = "{\"name\":\"张三\",\"gender\":1,\"age\":18}";
        //Json转对象
        UserInfo userInfo = objectMapper.readValue(s, UserInfo.class);
        System.out.println(userInfo.toString());
    }
}

我们使用了Jackson工具里面的ObjectMapper类的writeValueAsString()方法和readValue()方法来进行转换的。

这里我使用了一个@Test注解,这个注解类似于main方法,可以运行测试方法,这个注解在一个类里面可以有多个,一个类里面只能有一个main方法。

我们使用writeValueAsString()方法将对象转换成JSON,利用readValue()方法将JSON转换为对象,该方法参数里面要标明转成那个对象。

JSON的优点:

  1. 语法简单,便于阅读理解,能够快速的进行数据转换。
  2. JSON可以被多种语言解析,跨平台性好。
  3. JSON的数据格式消耗的带宽小,可以提高传输效率。
  4. JSON数据格式是纯文本格式,不是包含什么可执行代码,比较安全。
  5. JSON数据格式容易拓展,嵌套数组和对象等复杂类型。

传递JSON数据:

这里使用了@RequestBody注解,作用是将请求中的JSON数据转换成参数里面的对象类型,代码如下:

java 复制代码
    //传递JSON数据
    @RequestMapping("r10")
    public String r10(@RequestBody UserInfo userInfo) {
        return "接收的参数:" + userInfo.toString();
    }

利用postman构造请求,包含JSON的数据:

注意事项:

我们在test里面的代码,将JSON对象转为对象使用的是readValue()方法,这个代码工作原理是:利用对象对应的类里面的空的构造方法,先new一个对象,然后使用这个对象赋值类里面的属性,如果我们在类里面重写了带有参数的构造方法,没有空的构造方法,此时下面代码执行就会报错,不能实现JSON转对象的过程。

java 复制代码
    //Json转对象
    @Test
    void testJsonToObject() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        //构造一个Json
        String s = "{\"name\":\"张三\",\"gender\":1,\"age\":18}";
        //Json转对象
        UserInfo userInfo = objectMapper.readValue(s, UserInfo.class);
        System.out.println(userInfo.toString());
    }

2.4.8 获取URL中参数@PathVariable

我们打开一个今日头条网站,然后随便找两篇文章,我们可以看到这两篇文章的URL如下:

https://www.toutiao.com/trending/7576914740409139263/?rank=1\&log_from=b9be4e9e1f37d_1764233947866

https://www.toutiao.com/trending/7576991131788247049/?rank=2\&log_from=d5f8b1470f48c8_1764233959884

我们可以看到这两篇文章的资源路径中存在两个不同的数字,我们可以猜测后端接收到请求,是根据这两个数组来查询是哪一篇文章,把内容返回给浏览器。

那后端如何获取到URL里面的参数呢?

这里要用到@PathVariable注解来获取,这里URL的名字要和获取的参数名字相同,参数名字也可以修改成其他名字,这里的获取的参数要使用{ }来包含。

java 复制代码
    //获取URL的参数
    @RequestMapping("/article/{articleId}")
    public String r11(@PathVariable Integer articleId) {
        return "获取到的参数:" + articleId;
    }
    //获取多个参数
    @RequestMapping("/article/{type}/{articleId}")
    public String r12(@PathVariable Integer articleId, @PathVariable("type") String articleType) {
        return "获取到的参数:" + articleId + " " + articleType;
    }

使用postman构造请求:

2.4.9 上传文件@RequestPart

这里使用MultipartFile类型来接收文件,使用transferTo将获取到的文件保存在本地路径下。

@RequestPart注解还可以用来对参数重命名的。

java 复制代码
    //获取文件并存储本地
    @RequestMapping("/r13")
    public String r13(@RequestPart("file11") MultipartFile file) throws IOException {
        System.out.println(file.getOriginalFilename());
        //将文件保存到本地
        file.transferTo(new File("D:\\" + file.getOriginalFilename()));
        return "文件获取成功,并上传到本地";
    }

2.4.10 获取Cookie/Session

什么是Cookie:

cookie是客户端浏览器端存储的小型文本数据(键值对的形式),负责被动接收服务器发来的数据,然后存储到本地,等到发出请求时,自动将cookie中的数据存储在请求中。

http协议是一种无状态协议,就是说客户端和服务器的这次会话和下一次会话之间没有关系。

但是在实际开发中,会存在一些需要关联起来不同的会话。比如我们在网页登录自己的账号时候,下次进入网页就是直接登录好的页面。

**会话:**就是客户端和服务器建立的一次连接,就是一个会话,相当于打电话也是一次会话。

当一个客户端访问服务器时候,而这时候服务器就会生成一个身份令牌sessionId,将这个sessionId存储在响应里面的set-cookie字段中传给客户端,此时客户端的cookie就保存sessionId这个属性,下次请求时候将sessionId存储在请求的cookie字段中,发送给服务器,服务器就可以通过sessionId来确定是哪个客户端访问的服务器。

什么是Session:

在实际开发中,一个服务器需要处理很多个客户端的请求,也就会产生很多会话,那么服务器是如何区分那个请求是那个客户端发送来的呢?

这里服务器就会对每个客户端创建一个session对象,里面包括sessionId,以及客户端的信息等内容,session本质是一个哈希表,sessionId是key值,value是客户端的信息。

当一个客户端首次访问服务器,此时服务器就会创建一个session对象,生成sessionId,然后将这个sessionId放在响应的set-cookie字段中,返回给客户端,客户端的cookie中就会存储sessionId这个数据,等到下一次客户端发送请求,就会将这个sessionId数据放在请求的cookie字段发送给服务器,服务器就可以根据sessionId找到对应的客户端数据。

图示:

Cookie和Session的区别:

Cookie是客户端保存用户信息的一种机制,Session是服务器保护用户信息的一种机制。

Cookie和Session之间通过SessionId关联起来的。

Cookie和Session经常一起配合使用,但是也可以不一起使用,Cookie也可以保存一些其他数据在客户端,Session中的SessionId不一定非要Cookie/set-Cookie字段来传递,URL也可以。

获取Cookie

传统获取方法:

java 复制代码
    //传统获取Cookie的方法
    @RequestMapping("r14")
    public String r14(HttpServletRequest request, HttpServletResponse response) {
        Cookie[] cookies = request.getCookies();
        if(cookies != null) {
            for(Cookie cookie : cookies) {
                System.out.println(cookie.getName() + ":" + cookie.getValue());
            }
        }
        return "获取cookie成功";
    }

HttpServletRequest类是http请求的所有信息,HttpServletResponse类包含http响应的所有内容。

我们通过postman来设置cookie的值,然后发送请求,通过上面代码获取到cookie的键值对,然后返回字符串获取cookie成功。

简单获取cookie方法:

java 复制代码
    //通过注解拿到cookie的值
    @RequestMapping("/r15")
    public String r15(@CookieValue("name") String name) {
        return "从cookie中获取的值" + name;
    }

这里使用了@CookieValue注解来获取cookie的值。

获取Session

存储session:

java 复制代码
    //存储session
    @RequestMapping("/setSession")
    public String setSession(HttpServletRequest request) {
        //先从request里面获取sessionId,然后根据sessionId找到session对象。
        //这里默认为true,表示没有session就创建个session。
        //参数为false,表示没有session就返回null。
        HttpSession session = request.getSession(true);
        //设置用户名和年龄
        session.setAttribute("name", "lisi");
        session.setAttribute("age", 20);
        return "存储session成功";
    }

这里使用HttpServletRequest来获取到session,使用setAttribute方法来设置session的值。

获取session:

java 复制代码
    //获取session
    @RequestMapping("/getSession1")
    public String getSession1(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if(session == null) {
            return "没有session对象";
        }else {
            String userName = (String)session.getAttribute("name");
            return "用户名是:" + userName;
        }
    }

    //获取session
    @RequestMapping("/getSession2")
    public String getSession2(HttpSession session) {
        String userName = (String)session.getAttribute("name");
        return "用户名是:" + userName;
    }

    //获取session
    @RequestMapping("/getSession3")
    public String getSession3(@SessionAttribute("name") String userName) {
        return "用户名是:" + userName;
    }

有三种方法获取session,第三种使用了@SessionAttribute注解来获取session的值。

2.4.11 获取Header

java 复制代码
    //获取Header
    @RequestMapping("/getHeader1")
    public String getHeader1(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        return userAgent;
    }

    //获取Header
    @RequestMapping("/getHeader2")
    public String getHeader2(@RequestHeader("User-Agent") String userAgent) {
        return userAgent;
    }

这里获取Header中的键值对的属性值,存在两种方法,第二种使用了@RequestHeader注解来获取。

2.5 响应

2.5.1 返回静态页面 和 返回数据@ResponseBody

java 复制代码
@RequestMapping("/resp")
@Controller
public class RespController {
    //返回静态页面
    @RequestMapping("/r1")
    public String returnPage() {
        return "/index.html";
    }
    //返回数据
    @RequestMapping("/r2")
    @ResponseBody
    public String returnData() {
        return "返回的数据";
    }

}

这里我们使用了@Controller注解。

@Controller注解 和 @RestController注解的区别:

@RestController = @Controller + @ResponseBody,被@Controller注解修饰的类,会被扫描的。程序运行时候默认扫描的是启动类所在的目录下的类。

@Conteoller注解是该类返回的都是页面时候使用的。

@RestController注解是该类返回的都是数据的时候使用的。

如果一个类既有页面又有数据,我们可以使用@Controller注解,然后返回数据的方法上使用@ResponseBody注解,该注解既是类注解,也是方法注解。

2.5.2 返回HTML代码片段

java 复制代码
    //返回HTML代码片段
    @RequestMapping("r3")
    @ResponseBody
    public String returnHtml() {
        return "<h1>一级标题<h1>";
    }

    //返回HTML代码
    @RequestMapping(value = "r4", produces = "text/plain")
    @ResponseBody
    public String returnText() {
        return "<h1>一级标题<h1>";
    }

我们如果返回的是HTML的代码,浏览器会自动转换为HTML格式,我们可以通过设置Content-Type键值对的值来改变在浏览器显示的类型,使用produces参数来修改。

2.5.3 返回Json

java 复制代码
    //返回Json
    @RequestMapping("/r5")
    @ResponseBody
    public UserInfo returnJson() {
        UserInfo userInfo = new UserInfo("zhangsan",1,2);
        return userInfo;
    }

这里的返回结果为对象。

2.5.4 设置状态码

java 复制代码
    //设置响应的状态码
    @RequestMapping("/r6")
    @ResponseBody
    public UserInfo setStatus(HttpServletResponse response) {
        response.setStatus(404);
        UserInfo userInfo = new UserInfo("zhangsan",1,2);
        return userInfo;
    }

此时看到状态码被设置成404。

2.5.5 设置Header

java 复制代码
    //设置Header
    @RequestMapping("/r7")
    @ResponseBody
    public String setHeader(HttpServletResponse response) {
        response.setHeader("one","one");
        return "设置header成功";
    }

此时看到请求头多了一个设置的键值对。

2.5.6 设置Content-Type

java 复制代码
    //返回HTML代码
    @RequestMapping(value = "/r4", produces = "text/plain")
    @ResponseBody
    public String returnText() {
        return "<h1>一级标题<h1>";
    }

使用produces属性来设置。

3. 综合性练习

3.1 加法计算器

3.1.1 约定前后端交互接口

接口定义:

请求参数:

响应数据:

3.1.2 服务器代码

后端接收到两个参数后,返回数据显示在新页面里。

java 复制代码
@RequestMapping("/calc")
@RestController
public class CalcController {
    @RequestMapping("/sum")
    public String sum(Integer num1, Integer num2) {
        if(num1 == null || num2 == null) {
            return "输入的参数不合法,请重新输入";
        }
        Integer sum = num1 + num2;
        return "<h1>计算机计算结果:" + sum + "</h1>";
    }
}

3.1.3 前端代码

这里使用的from标签来发送请求,action是请求的URL,method是请求的方式,input标签的name属性是key的值,输入的是value的值,type="submit"是提交按钮,点击后将from标签域内的数据提交给服务器。

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>
    <!-- 使用from标签来向服务器发送请求 -->
     <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>
</body>

</html>

3.1.4 运行测试

3.2 用户登录

3.2.1 约定前后端交互接口

用户登录接口:

请求参数:

响应数据:

Content-Type : text/html

响应内容:

true //账号密码验证成功

false //账号密码验证失败

第二个接口:

请求数据:

请求路径:/user/getLoginUser

请求方式:GET

接口描述:查询当前登录的用户

请求参数:无
响应数据:

Content-Type:text.html

响应内容:zhangsan

3.2.2 服务器代码

服务器代码实现了两个接口,一个是登录页面,接收账号和密码,并且将账号的信息存储在session中,如果账号密码正确返回true,其余情况返回false。

一个是显示登录用户名字的页面,根据session查询登录用户的名字返回给前端。

java 复制代码
@RequestMapping("/userL")
@RestController
public class UserLoginController {
    @RequestMapping("/login")
    public boolean login(String userName, String password, HttpSession session) {
        //参数的校验
        //方法1:
//        if(userName == null || userName == "" || password == null || password == "") {
//            return false;
//        }
        //方法2:
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {
            return false;
        }
        //这里使用固定的账号密码
        if("admin".equals(userName) && "123456".equals(password)) {
            //将账号信息存储在session中
            session.setAttribute("userName", userName);
            return true;
        }
        return false;
    }
    @RequestMapping("/getLoginUser")
    public String getLoginUser(HttpSession session) {
        String userName = (String)session.getAttribute("userName");
        return userName;
    }
}

3.2.3 前端代码

登录界面:

这里使用了Ajax技术,作用是可以保证页面不重新加载页面,跟服务器交换数据,更新局部页面,下面使用的jQuery语法来创建Ajax请求。

html 复制代码
<body>
  <h1>用户登录</h1>
  用户名:<input name="userName" type="text" id="userName"><br>
  密码:<input name="password" type="password" id="password"><br>
  <input type="button" value="登录" onclick="login()">
  
  <script src="js/jquery-3.7.1.min.js"></script>
  <script>
    // 创建login函数
    function login() {
      // 使用jQuery来创建Ajax请求
      $.ajax({
        url: "/userL/login",
        type: "post",
        //发送到服务端的数据
        data: {
          userName: $("#userName").val(),
          password: $("#password").val()
        },
        //http请求成功
        success: function(response) {
          if(response) {
            //跳转到新的页面
            location.href = "index.html";
          }else {
            alert("密码错误,请重新输入");
          }
        }
      });
    }
  </script>
</body>

用户登录成功首页:

这里也是用了Ajax技术来实现的。

html 复制代码
<body>
    登录人: <span id="loginUser"></span>

    <script src="js/jquery-3.7.1.min.js"></script>
    <script>
        $.ajax({
            url: "userL/getLoginUser",
            type: "get",
            success: function(userName) {
                $("#loginUser").text(userName);
            }
        });
    </script>
</body>

3.2.4 运行测试

3.3 留言板

3.3.1 约定前后端交互接口

获取全部留言:

请求:GET

url:message/getList
响应:JSON格式

{ "from": "A", "to": "B", "message": "C" },{ "from": "D", "to": "E", "message": "F" }

发表新的留言:

请求: POST JSON格式

url:/message/publish

{

"from": "A",

"to": "B",

"message": "C"

}
响应:JSON格式

{

ok: 1

}

3.3.2 服务器代码

lombok介绍:

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

我们使用时候,需要导入依赖:

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

lombok库里面包含很多注解:

@Data注解是直接包含很多的方法,在编译时候就会加到类里面的,此时注解会消失。

下面这个类,可以换成添加注解的形式:

java 复制代码
public class UserInfo {
    private String name;
    private int gender;
    private int age;

    public UserInfo() {

    }
    public UserInfo(String name, int gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "name='" + name + '\'' +
                ", gender=" + gender +
                ", age=" + age +
                '}';
    }
}

下面代码在编译之后,就会包含上面的各种方法。

java 复制代码
@Data
public class UserInfo {
    private String name;
    private int gender;
    private int age;
}
引入依赖的新方式:

使用EditStarter插件。

我们就可以通过上面页面添加依赖了。

服务器代码实现:

先定义一个类来表示一条留言对象。

java 复制代码
@Data
public class MessageInfo {
    private String from;
    private String to;
    private String message;
}

设置两个接口,一个是接收留言数据,显示在下面,另一个是保存留言数据在顺序表中,保证留言信息不会刷新掉。

java 复制代码
@RequestMapping("/message")
@RestController
public class MessageController {
    private List<MessageInfo> messageInfoList = new ArrayList<>();

    @PostMapping("/publish")
    public String publish(@RequestBody MessageInfo messageInfo) {
        //使用@RequestBody注解,将JSON字符串转成对象
        //检查参数规范
        if(!StringUtils.hasLength(messageInfo.getFrom()) ||
                !StringUtils.hasLength(messageInfo.getTo()) ||
                !StringUtils.hasLength(messageInfo.getMessage())) {
            return "{\"ok\": 0}";
        }
        //存储留言
        messageInfoList.add(messageInfo);
        return "{\"ok\": 1}";
    }

    @GetMapping("/getList")
    public List<MessageInfo> getList() {
        return messageInfoList;
    }
}

3.3.3 前端代码

前端使用JSON.stringify()方法将对象转为JSON字符串,使用JSON.parse()方法将JSON字符串转为对象。

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-3.7.1.min.js"></script>
    <script>
        load();
        //显示所有留言
        function load() {
            $.ajax({
                type: "get",
                url: "/message/getList",
                success: function(messages) {
                    if(messages != null && messages.length > 0) {
                        var finalHtml = "";
                        for(var m of messages) {
                            finalHtml += "<div>" + m.from + "对" + m.to + "说" + m.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向后端发送请求
            $.ajax({
                type: "post",
                url: "/message/publish",
                contentType: "application/json",
                //stringify方法将对象转为JSON字符串
                data: JSON.stringify(data),

                success: function (result) {
                    //使用parse方法将JSON字符串转成对象
                    var str = JSON.parse(result);
                    if(str.ok == 1) {
                        //成功
                        var divE = "<div>" + from + "对" + to + "说" + say + "</div>";
                        //将内容添加到留言板下面
                        $(".container").append(divE);
                        //清空输入内容
                        $("#from").val("");
                        $("#to").val("");
                        $("#say").val("");
                    }else {
                        //失败
                        alert("留言发布失败");
                    }
                }
            });
        }
    </script>
</body>
</html>

3.3.4 运行测试

3.4 图书管理系统

3.4.1 生成前端页面的工具

Bootstarp网站,这里面可以有很多模版和样式,可以直接使用。

https://www.bootcss.com/

3.4.2 约定前后端交互接口

登录接口:

url /user/login

post

请求参数:name=admin&password=admin

响应:

true //账号密码正确

false //账号密码错误

图书列表展示:

url /book/getList

post

请求参数:无

响应:

返回图书列表

{ "id": 1, "bookName": "活着", "author": "余华", "count": 270, "price": 20, "publish": "北京⽂艺出版社",17 "status": 1, "statusCN": "可借阅" }, ........

字段说明:

3.4.3 服务器代码

用户登录接口:

java 复制代码
@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) && "123456".equals(password)) {
            session.setAttribute("userName", name);
            return true;
        }
        return false;
    }
}

图书列表接口:

java 复制代码
@RequestMapping("/book")
@RestController
public class BookController {
    @RequestMapping("/getList")
    public List<BookInfo> getList() {
        //这里没有使用数据库,先mock数据(自己构造虚假数据)
        List<BookInfo> bookInfos = mockData();
        //这里返回给前端的图书借阅状态设置成文字
        for(BookInfo book : bookInfos) {
            if(book.getStatus() == 1) {
                book.setStatusCN("可借阅");
            }else {
                book.setStatusCN("不可借阅");
            }
        }
        return bookInfos;
    }

    //该方法从mock的数据中查数据
    public List<BookInfo> mockData() {
        List<BookInfo> bookInfos = new ArrayList<>();
        for (int i = 1; i <= 15 ; i++) {
            BookInfo book = new BookInfo();
            book.setId(i);
            book.setBookName("图书" + i);
            book.setAuthor("作者" + i);
            book.setCount(new Random().nextInt(80) + 21); //[20,100]
            book.setPrice(new BigDecimal(new Random().nextInt(80) + 21));
            book.setPublish("图书出版社" + i);
            book.setStatus(i % 5 == 0 ? 2 : 1); //1-可借阅 2-不可借阅
            bookInfos.add(book);
        }
        return bookInfos;
    }
}

3.4.4 前端代码

登录页:

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/login",
                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>
            </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="' + book.id + '" id="selectBook" class="book-select"></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 + '">修改</a>';
                            finalHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + book.id + ')">删除</a>';
                            finalHtml += '</div></td></tr>';
                        }
                        $("tbody").html(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.4.5 运行测试

4. 应用分层

为什么要应用分层?

我们前面写的代码创建了很多类,这些类混在一起就会难以阅读和区分,不方便别人阅读自己的代码,此时就需要一种规范来将代码进行分类,类似于spring MVC框架将系统分为Model(模型),View(视图),Controller(控制器)三个层次,将用户视图和业务处理完全分开,通过控制器来进行连接。

现在主流的开发模式是"前后端分离",前端和后端各自进行开发,又在Java后端产生了新的三层架构:

表现层 :用来接受请求和返回响应的,表现层一般使用Controller来表示。

业务逻辑层 :用来处理具体的业务的,业务逻辑层一般使用Sevice来表示。

数据层 :用来管理和存储数据的,数据层一般使用Dao来表示。

实体类可以使用 model , entity , pojo 这些来表示。

MVC模式和三层架构的联系:

MVC模型主要是把数据和视图分离,将数据展示和数据处理分开,通过控制器来建立联系。

三层架构是从不同的维度处理数据的高内聚和低耦合,将交互逻辑,业务处理,数据库管理分开。

5. 总结

本文介绍了很多注解:

@RequestMapping:路由映射,用来表示URL的。

@RequestParam:对前端传过来的参数名字进行重命名。

@RequestBody:接收前端传来的JSON类型的参数的。

@PathVariable:接受路径中的参数的。

@RequestPart:接收前端传来的文件的。

@ResponseBody:返回数据的。

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

@RestController:@ResponseBody + @Controller 返回数据。

@CookieValue:从Cookie中获取值。

@SessionAttribute:从Session中获取值。

@RequestHeader:从Header中获取值。

Spring MVC内置的HttpServletRequest 和 HttpServletResponse两个对象,可以获得请求和响应中的所有参数,Cookie和session可以从HttpServletRequest中获取,可以使用HttpServletResponse来设置状态码。

相关推荐
前端不太难2 小时前
RN 构建包体积过大,如何瘦身?
前端·react native
小光学长2 小时前
基于web的影视网站设计与实现14yj533o(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·前端·数据库
vocoWone2 小时前
📰 前端资讯 - 2025年12月10日
前端
周杰伦_Jay2 小时前
【JVM深度解析】运行时数据区+类加载+GC+调优实战(附参数示例)
java·jvm·spring boot·分布式·架构·java-ee
李少兄2 小时前
前端开发中的多列布局(Multi-column Layout)
前端·css
new出一个对象2 小时前
uniapp手写滚动选择器
开发语言·前端·javascript
Data_agent2 小时前
京东获得京东商品详情API,python请求示例
java·前端·爬虫·python
CodeSheep2 小时前
这个知名编程软件,正式宣布停运了!
前端·后端·程序员
2401_860319522 小时前
DevUI组件库实战:从入门到企业级应用的深度探索,如何实现带搜索的Table表格
前端·前端框架