当前位置: 首页 > news >正文

Spring Web MVC 入门

1. 什么是 Spring Web MVC

Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从从⼀开始就包含在Spring框架中。它的 正式名称“SpringWebMVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为"Spring MVC".

什么是Servlet呢?

Servlet 是⼀种实现动态⻚⾯的技术.准确来讲Servlet是⼀套JavaWeb开发的规范,或者说是⼀套 Java Web开发的技术标准.只有规范并不能做任何事情,必须要有⼈去实现它.所谓实现Servlet规 范,就是真正编写代码去实现Servlet规范提到的各种功能,包括类、⽅法、属性等. Servlet 规范是开放的,除了Sun公司,其它公司也可以实现Servlet规范,⽬前常⻅的实现了 Servlet 规范的产品包括Tomcat、Weblogic、Jetty、Jboss、WebSphere等,它们都被称 为"Servlet 容器".Servlet 容器⽤来管理程序员编写的Servlet类

从上述定义我们可以得出一个信息:Spring Web MVC 是一个 Web 框架

以下简称为 Spring MVC

2. MVC 定义

MVC是ModelViewController的缩写,它是软件⼯程中的⼀种软件架构设计模式,它把软件系统分 为模型、视图和控制器三个基本部分

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

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

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

⽐如去饭店吃饭

客⼾进店之后,服务员来接待客⼾点餐,客⼾点完餐之后,把客⼾菜单交给前厅,前厅根据客⼾菜单 给后厨下达命令.后厨负责做饭,做完之后,再根据菜单告诉服务员,这是X号餐桌客⼈的饭. 在这个过程中

服务员就是View(视图),负责接待客⼾,帮助客⼾点餐,以及给顾客端饭

前厅就是Controller(控制器),根据⽤⼾的点餐情况,来选择给哪个后厨下达命令

后厨就是Model(模型),根据前厅的要求来完成客⼾的⽤餐需求

3. Spring MVC

MVC是⼀种架构设计模式,也是⼀种思想, ⽽SpringMVC是对MVC思想的具体实现.除此之外, Spring MVC还是⼀个Web框架.

总结来说,SpringMVC是⼀个实现了MVC模式的Web框架.

所以,SpringMVC主要关注有两个点:

1. MVC

2. Web 框架

前面创建 Spring Boot 项目时,勾选的 Spring Web 框架就是 Spring MVC 框架

Spring Boot 是实现 Spring MVC 的一种方式,Spring Boot 可以添加很多依赖,借助这些依赖实现不同功能,其通过添加 Spring Web MVC 框架来实现 Web 功能

而 Spring 在实现 MVC 时,也结合自身项目特点,做了一些改变,相对而言,用下面这个图来描述 Soring MVC 更加合适一些:

4. 使用 Spring MVC

使用 Spring MVC 就是通过浏览器和用户程序进行交互

主要分为以下三个方面:

1. 建立连接:将用户(浏览器)和 Java 程序连接起来,也就是访问一个地址能够调用到我们的 Spring 程序

2. 请求:用户请求的时候会带一些参数,在程序中要想办法获取参数,所以请求主要是获取参数的功能

3. 响应:执行了业务逻辑之后,要把程序执行的结果返回给用户,也就是响应

4.1 项目准备

Spring MVC 项目创建和 Spring Boot 创建项目相同,在创建的时候选择 Spring Web 就相当于创建了 Spring MVC 的项目

4.2 建立连接

在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由映射,也就是浏览器连接程序的作用

创建一个 UserController 类,实现用户通过浏览器和程序的交互,代码如下:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserController {@RequestMapping("/sayHi")public String sayHi() {return "Hello Spring MVC";}
}

tip:方法名和路径名可以不同

接下来访问:http://127.0.0.1:8080/sayHi 就可以看到程序返回的数据了

4.2.1 @RequestMapping 注解介绍

@RequestMapping 是 Spring Web MVC 应用程序中最常被用到的注解之一,它是用来注册接口的路由映射的

表示服务收到请求时,路径为 /sayHi 的请求就会调用 sayHi 这个方法的代码

路由映射:当用户访问一个 URL 时,将用户的请求对应到程序中某个类的某个方法的过程叫做路由映射

其与 @RequestController 需要同时存在,若注释掉 @RequestController

一个项目中会有很多类,每个类中又有很多方法,当我们要访问 /sayHi 时,Spring 会对所有类进行扫描,只有类加了注解 @RequestController,Spring 才会去看这个类里面有没有 @RequestMapping("/sayHi"),如果有,就执行该方法;若是类没有加注解 @RequestController,Spring就不会进入该类,即使该类中有 @RequestMapping("/sayHi")

4.2.2 @RequestMapping 使用

@RequestMapping 既可以修饰类,也可以修饰方法,当修饰类和方法时,访问的地址是类路径 + 方法路径

@RequestMapping 标识一个类:设置映射请求的请求路径的初始信息

@RequestMapping 标识一个方法:设置映射请求路径的具体信息

@RequestMapping("/user")
@RestController
public class UserController {@RequestMapping("/sayHi")public String sayHi() {return "Hello Spring MVC";}
}

访问地址:http://127.0.0.1:8080/user/sayHi

tip:

1. @RequestMapping 的 URL 路径最前面加不加 / 都可以,Spring 程序启动时会进行判断,如果前面没有加 /,Spring 会拼接上一个 /通常情况下,建议加上 /

2. 注解没有先后顺序之分

4.2.3  @RequestMapping 是 GET 还是 POST 请求

GET 请求:

浏览器发送的请求类型都是 get,通过上面案例可知 @RequestMapping 支持 get 请求

通过 Fiddler 也可以观察到:

POST 请求:

通过 form 表单来构造请求:创建 test.html:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><form action="/user/sayHi" method="post"><input type="submit" value="提交"></form>
</body>
</html>

从运行结果可以看出:@RequestMapping 既支持 get 请求,又支持 post 请求,同理,也支持其他的请求方式

4.2.4 指定 GET/POST 方法类型

方法一:我们可以显式的指定 @RequestMapping 来接收 POST 的情况,如下:

方法二:使用 @GetMapping 或 @PostMapping 注解来设置

@PostMapping 使用方法同理


5. 使用 Postman 创建请求

界面介绍

6. 请求

6.1 传递单个参数

接收单个参数,在 Spring MVC 中直接用方法中的参数就可以,如下代码:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RequestMapping("/param")
@RestController
public class ParamController {@RequestMapping("/p1")public String p1(String name) {return "接收到参数,name:" + name;}
}

使用 Postman 构建发送请求:

Spring MVC 根据方法的参数名,找到对应的参数,赋值给方法

如果参数不一致,是获取不到参数的,如下:

tip:

使用基本类型来接收参数时,参数必须传(除非是 boolean 类型),否则会报 500 错误

类型不匹配时,会报 400 错误

使用包装类型,如果不传参数,Spring 接收到的数据则为 null,因此在企业开发中,对于参数可能为空的数据,建议使用包装类型

6.2 传递多个参数

和接收单个参数一样,直接使用方法的参数接收即可,如下:

tip:当有多个参数时,前后端进行参数匹配时,是以参数的名称进行匹配的,因此参数的位置是不影响后端获取参数的结果

6.3 传递对象

当参数比较多时,方法声明就需要有很多形参,并且后续每新增一个参数,也需要修改方法声明

因此我们不妨将这些参数封装成一个对象

Spring MVC 也可以自动实现对象参数的赋值,如下:

创建一个 User 对象:

package com.example.sprignmvc_demo_20241021;public class User {private String name;private int age;private Integer gender;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Integer getGender() {return gender;}public void setGender(Integer gender) {this.gender = gender;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +'}';}
}

传递对象代码:

Spring 会根据参数名称自动绑定到对象的各个属性上,如果某个属性未传递,则赋值为 null(基本类型则赋值为默认初始值,如 int 被赋值为 0)

6.4 后端参数重命名(后端参数映射)

某些特殊情况下,前端传递的参数 key 和我们后端接收的 key 可以不一致,如:前端传递了一个 userName,而后端是使用 name 字段来接收的,这样就会出现参数接收不到的情况,如果出现这种情况,我们就可以使用 @RequestParam 重命名前后端的参数值,如下:

若此时前端使用 name 来传参:

查看 @RequestParam 的源码:

可以看到,required 的默认值为 true,表示的含义是:该注解修饰的参数默认为必传参数

既然有默认,就可以更改:

再次运行:

不报错了,但是接收不到前端传的 name 参数,这是因为使用 @RequestParam 进行参数重命名时,请求参数只能和 @RequestParam 声明的名称一致,才能进行参数绑定和赋值

6.5 传递数组

Spring MVC 可以自动绑定数组参数的赋值

像第二种方式,会自动进行分割,如下:

6.6 传递集合

这种传参方式和传数组是一样的,HTTP 默认将其封装成了一个数组,相当于是传递了一个数组,而 List 无法用来接收该数组

此时就需要使用 @RequestParam 来进行参数绑定,将数绑定成 List

6.7 传递 JSON 数据

6.7.1 JSON 概念

JSON:JavaScriptObjectNotation 【JavaScript 对象表⽰法】

JSON是⼀种轻量级的数据交互格式.它基于ECMAScript(欧洲计算机协会制定的js规范)的⼀个⼦集, 采⽤完全独⽴于编程语⾔的⽂本格式来存储和表⽰数据。--百度百科

简单来说:JSON就是⼀种数据格式,有⾃⼰的格式和语法,使⽤⽂本表⽰⼀个对象或数组的信息,因此 JSON本质是字符串. 主要负责在不同的语⾔中数据传递和交换.

6.7.2 JSON 和 JavaScript 的关系

没有关系,只是语法相似

6.7.3 JSON 语法

JSON 是一个字符串,其格式非常类似于 JavaScript 对象字面量的格式

看一段 JSON 数据:

{"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"]}]}

JSON 的语法:

数据在 键值对(Key / Value)

数据由 逗号 分隔

对象用 { } 表示

数组用 [ ] 表示

值可以为对象,也可以为数组,数组中可以包含多个对象

JSON 的两种结构:

对象:大括号 { } 保存的对象是一个无序的 键值对 集合,一个对象以 左括号 { 开始,右括号 } 结束,每个 后跟一个冒号 : ,键值对使用 逗号 分隔

数组:中括号 [ ] 保存的数组是 值(Value) 的有序集合,一个数组以 左中括号 [ 开始,右中括号 ] 结束,值之间使用 逗号 分隔

6.7.4 JSON 字符串和 Java 对象互转

JSON 本质上是一个字符串,通过文本来存储和描述数据

Spring MVC 框架也集成了 JSON 的转换工具,我们可以直接使用,来完成 JSON 字符串和 Java 对象的互转

本质上是 jackson-databind 提供的转换功能,Spring MVC 框架中已经把该工具包引入了进来,我们可以直接使用,若要脱离 Spring MVC 使用,需要引入相关依赖,如下:

<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.5</version>
</dependency>

JSON 的转换工具包有很多,jackson-databind 只是其中的一种

后端实现:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;public class JsonTest {public static void main(String[] args) throws JsonProcessingException {ObjectMapper objectMapper = new ObjectMapper(); // 该类来自于 jackson-databind,用于处理 JSONUser user = new User();user.setName("lisi");user.setAge(18);user.setGender(1); // 表示 男// 对象转 JSONString s = objectMapper.writeValueAsString(user);System.out.println(s);// JSON 转对象User user1 = objectMapper.readValue(s, User.class);System.out.println(user1);}
}

使用 ObjectMapper 对象提供的两个方法,可以完成对象和 JSON 字符串的互转

writeValueAsString:把对象转为 JSON 字符串

readValue:把字符串转为对象

6.7.5 JSON 优点

简单易⽤:语法简单,易于理解和编写,可以快速地进⾏数据交换

跨平台⽀持: JSON可以被多种编程语⾔解析和⽣成,可以在不同的平台和语⾔之间进⾏数据交换和 传输

轻量级:相较于XML格式,JSON数据格式更加轻量级,传输数据时占⽤带宽较⼩,可以提⾼数据传输 速度

易于扩展: JSON的数据结构灵活,⽀持嵌套对象和数组等复杂的数据结构,便于扩展和使⽤

安全性:JSON数据格式是⼀种纯⽂本格式,不包含可执⾏代码,不会执⾏恶意代码,因此具有较⾼ 的安全性

6.8 传递 JSON 对象

接收 JSON 对象,需要使用 @RequestBody 注解

RequestBody:请求正文,这个注解作用在请求正文的数据绑定,请求参数必须写在请求正文中

后端实现:

    @RequestMapping("/p8")public String p8(@RequestBody User user) {return "user:" + user;}

tip:区分传递对象时使用 get 和 post 方式

区分传递对象与传递 JSON 对象

6.9 获取 URL 中参数(@PathVariable)

path variable:路径变量

这个注解主要作用在请求 URL 路径上的数据绑定

默认传递餐宿写在 URL 上,Spring MVC 就可以获取到

多个参数的情况:

tip:如果方法参数名称和需要绑定的 URL 中的变量名称一致时,可以简写,不用给 @PathVariable 的属性赋值,如上述例子中的 name 变量;反之则需要

6.10 传递文件(@RequestPart)

重命名:

6.11 获取 Cookie/Session

6.11.1 Cookie

HTTP 协议自身是属于 “无状态” 协议

无状态是指:默认情况下 HTTP 协议的客户端和服务器之间的这次通信和下次通信之间没有直接的联系

但是实际开发中,我们很多时候是需要知道请求之间的关联关系的

例如:登录网站成功后,第二次访问的时候,服务器就能知道该请求是否已经登录过了

上述途中的 “令牌” 通常就存储在 Cookie 字段中

⽐如去医院挂号

  1. 看病之前先挂号.挂号时候需要提供⾝份证号,同时得到了⼀张"就诊卡",这个就诊卡就相当于患 者的"令牌".
  2. 后续去各个科室进⾏检查,诊断,开药等操作,都不必再出⽰⾝份证了,只要凭就诊卡即可识别出当 前患者的⾝份.
  3. 看完病了之后,不想要就诊卡了,就可以注销这个卡.此时患者的⾝份和就诊卡的关联就销毁了.(类 似于⽹站的注销操作)
  4. ⼜来看病,可以办⼀张新的就诊卡,此时就得到了⼀个新的"令牌"

此时在服务器这边就需要记录 “令牌” 信息,以及令牌对应的用户信息,这个就是 Cookie机制所作的工作

6.11.2 Session

会话:就是对话的意思

在计算机领域,会话是一个客户与服务器之间的不中断的请求响应,对客户的每个请求,服务器能识别出请求来自于同一个客户

当一个位置的客户像 Web 应用程序发送第一个请求时,就开始了一个会话

当客户明确结束会话或服务器在一个时限内没有接收到客户的任何请求时,会话就结束了

⽐如我们打客服电话

每次打客服电话,是⼀个会话.挂断电话,会话就结束了

下次再打客服电话,⼜是⼀个新的会话.

如果我们⻓时间不说话,没有新的请求,会话也会结束.

服务器同一时刻收到的请求是很多的,服务器需要清楚的区分每个请求是属于哪个用户,也就是属于哪个会话,就需要在服务器这边记录每个会话以及与用户的信息的对应关系

Session 是服务器为了保存用户信息而创建的一个特殊的对象

Session 的本质就是一个 “哈希表”,存储了一些键值对结构,Key 就是 SessionID,Value 就是用户信息(用户信息可以根据需求灵活设计)

SessionID 是由服务器生成的一个 “唯一性字符串”,从 Session 机制的角度来看,这个唯一性字符串称为 “SessionID”,但是站在整个登录流程中看待,也可以把这个唯一性字符串称为 “token”

上述例子中的 令牌ID 就可以看作是 SessionID,只不过令牌除了 ID 之外,还会带有一些其他信息,比如时间、签名等

  1. 当用户登录的时候,服务器在 Session 中新增一个新记录,并把 SessionID 返回给客户端(通过 HTTP 响应中的 Set-Cookie 字段返回)
  2. 客户端后续再给服务器发送请求的时候,需要在请求中带上 SessionID(通过 HTTP 请求中的 Cookie 字段带上)
  3. 服务器收到请求之后,根据请求中的 SessionID 在 Session 信息中获取到对应的用户信息,再进行后续操作,找不到则重新创建 Session,并把 SessionID 返回

tip:Session 默认是保存在内存中的,如果重启服务器则 Session 数据就会丢失

6.11.3 Cookie 和 Session 的区别

  • Cookie 是客户端保存用户信息的一种机制;Session 是服务器端保存用户信息的一种机制
  • Cookie 和 Session 之间主要是通过 SessionID 关联起来的,SessionID 是 Cookie 和 Session 之间的桥梁
  • Cookie 和 Session 经常会在一起配合使用,但不是必须配合

完全可以用 Cookie 来保存一些数据在客户端,这些数据不一定是用户身份信息,也不一定是 SessionID

Session 中的 SessionID 也不是非得通过 Cookie / Set-Ckkoie 传递,比如通过 URL 传递

6.11.4 获取 Cookie

1) 传统方法获取:

    // 获取 Cookie@RequestMapping("/getCookie")public String getCookie(HttpServletRequest request) {Cookie[] cookies = request.getCookies();// cookie 为空判断// if (cookies == null) return "Cookie 为 null";for (Cookie cookie : cookies) {System.out.println(cookie.getName() + ":" + cookie.getValue());}return "Cookie 获取成功";}
  • Spring MVC是基于ServletAPI构建的原始Web框架,也是在Servlet的基础上实现的
  • HttpServletRequest , HttpServletResponse 是Servlet提供的两个类,是Spring MVC⽅法的内置对象.需要时直接在⽅法中添加声明即可.
  • HttpServletRequest 对象代表客⼾端的请求,当客⼾端通过HTTP协议访问服务器时,HTTP请 求头中的所有信息都封装在这个对象中,通过这个对象提供的⽅法,可以获得客⼾端请求的所有信 息.
  • HttpServletResponse 对象代表服务器的响应.HTTP响应的信息都在这个对象中,⽐如向客⼾ 端发送的数据,响应头,状态码等.通过这个对象提供的⽅法,可以获得服务器响应的所有内容
  • Spring MVC在这两个对象的基础上进⾏了封装,给我们提供更加简单的使⽤⽅法.

此时没有设置 Cookie,通过浏览器访问 http://127.0.0.1:8080/header/getCookie,得到 Cookie 为 null

在浏览其中设置 Cookie 的值(手动添加 Cookie)

再次访问:

2) 简洁方法获取:

    @RequestMapping("/getCookie2")public String getCookie2(@CookieValue("zhangsan") String value) {return "从 Cookie 中获取信息:" + value;}

运行结果:

两种方式对比:

第一种方法可以获取多次参数

第二种方法每获取一个参数都需要加一个注解


tip:Postman 设置 Cookie


6.11.5 获取 Session

1) Session 存储和获取

Session 是服务器端的机制,我们需要先存储,才能获取

Session 也是基于 HttpServletRequest 来存储和获取的

2) Session 存储

    @RequestMapping("/setSession")public String setSession(HttpServletRequest request) {// 先获取到 session 对象,若没有,则创建一个空 sessionHttpSession session = request.getSession();session.setAttribute("userName", "zhangsan");session.setAttribute("age", 18);return "设置 session 成功";}

第一次运行上述程序,是没有 session 对象的,可以在 Fiddler 中观察到 Set-Cookie 生成 SessionID:

3) Session 读取(使用 HttpServletRequest)

    @RequestMapping("/getSession")public String getSession(HttpServletRequest request) {HttpSession session = request.getSession();// session 是类似 map 的结构// 判空if (session.getAttribute("userName") == null) return "session 为 null";String userName = (String)session.getAttribute("userName");System.out.println(session.getAttribute("age"));return "从 session 中获取信息,userName:" + userName;}

request.getSession() 共做了两件事:先从 Cookie 中拿到 SessionID,然后从 SessionID 中拿到 Session

设置完 session 后,再获取 session:

可以看到,Http 请求时,将 SessionID 通过 Cookie 传递到了服务器

4) 简洁获取①

    @RequestMapping("/getSession2")public String getSession2(HttpSession session) {String userName = (String)session.getAttribute("userName");System.out.println(session.getAttribute("age"));return "从 session 中获取信息,userName:" + userName;}

tip:Session 存储在服务器的内村上,服务重启时,Session 会丢失

先设置 session,再获取

5) 简洁获取②

    @RequestMapping("/getSession3")public String getSession3(@SessionAttribute String userName) {return "从 session 中获取信息,userName:" + userName;}

tip:由于 Session 是服务器端的,只能由代码去构建,不能用 Postman 构建


6.11.6 获取 Header

1) 传统方式:

获取 Header 也是从 HttpServletRequest 中获取

    @RequestMapping("/getHeader")public String getHeader(HttpServletRequest request) {String userAgent = request.getHeader("User-Agent");return "User-Agent:" + userAgent;}

2) 简洁方式:

    @RequestMapping("/getHeader2")public String getHeader2(@RequestHeader("User-Agent") String userAgent) {return "User-Agent:" + userAgent;}

post 方式:在 Headers 选项中根据需求添加即可

7. 响应

7.1 返回静态页面

创建前端页面 hello.html

html 代码:

后端代码:

@RequestMapping("/response")
@RestController
public class ResponseController {@RequestMapping("/returnHtmlPage")public String returnHtmlPage() {return "/hello.html";}
}

我们期待返回如下界面:

结果却是:

这时需要将 @RestController 改为 @Controller:

7.1.1 @RestController 和 @Controller

二者的差异就在 @RequestBody 上,@ResponseBody表示返回数据

早期前后端未分离时,后端需要返回视图,就用到 @Controller

现在前后端分离,后端仅需给前端返回数据,具体展示哪个页面给用户由前端决定,因此用到 @ResponseBody,再加上 @Controller 中其他的作用,就成了现在的 @RequestController

tip:

@Controller:定义一个控制器,Spring 框架启动时加载,把这个对象交给 Spring 管理

@ResponseBody:定义返回的数据格式为非视图,返回一个 text/html 信息

7.2 返回数据 @ResponseBody

@ResponseBody 既是类注解,又是方法注解

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

如果作用在方法上,表示该方法返回的是数据,其他方法不受影响

tip:将 returnData 方法上的 @ResponseBody 去掉,程序会报 404 错误

程序会认为需要返回的是视图,根据内容 "hahahahahahaha" 去查找文件,但是查找不到,路径不存在,报 404 异常

7.3 返回 HTML 代码片段

直接返回即可,当后端返回数据,若数据中有 HTML 代码,会被浏览器直接解析

通过 Fiddler 观察响应结果 Content-Type: text/html

响应中的 Content-Type 常见取值有以下几种:

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

如果请求的是 js 文件,Spring MVC 会自动设置 Content-Type 为 application / javascript

如果请求的是 css 文件,Spring MVC 会自动设置 Content-Type 为 text / css

7.4 返回 JSON

7.5 设置状态码

Spring MVC 会根据我们方法的返回结果自动设置响应状态码,也可以手动指定状态码

通过 Spring MVC 的内置对象 HttpServletResponse 提供的方法来进行设置

    @ResponseBody@RequestMapping("/setStatus")public User setStatus(HttpServletResponse response) {User user = new User();user.setName("zhangsan");user.setAge(18);response.setStatus(500);return user;}

7.6 设置 Header

http 响应报头也会向客户端传递一些附加信息,比如服务程序的名称;请求的资源已移动到新地址等,比如:Content-Type,Local 等

这些信息通过 @RequestMapping 注解的属性来实现,其源码如下:

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

7.6.2 通过 HttpServletResponse 内置方法设置 Header

7.6.3 通过 HttpServletResponse 内置方法设置 Cookie

8. 案例

8.1 加法计算器

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

1. 约定前后端交互接口

概念:

约定"前后端交互接⼝"是进⾏Web开发中的关键环节.

接⼝⼜叫API(ApplicationProgrammingInterface),我们⼀般讲到接⼝或者API,指的都是同⼀个东 西.

是指应⽤程序对外提供的服务的描述,⽤于交换信息和执⾏任务

简单来说,就是允许客⼾端给服务器发送哪些HTTP请求,并且每种请求预期获取什么样的HTTP响应.

现在"前后端分离"模式开发,前端和后端代码通常由不同的团队负责开发.双⽅团队在开发之前,会提前 约定好交互的⽅式.客⼾端发起请求,服务器提供对应的服务.服务器提供的服务种类有很多,客⼾端按 照双⽅约定指定选择哪⼀个服务.

接⼝,其实也就是我们前⾯⽹络模块讲的的"应⽤层协议".把约定的内容写在⽂档上,就是"接⼝⽂档",接 ⼝⽂档也可以理解为是应⽤程序的"操作说明书".

在项⽬开发前,根据需求先约定好前后端交互接⼝,双⽅按照接⼝⽂档进⾏开发.

接⼝⽂档通常由服务提供⽅来写,交由服务使⽤⽅确认,也就是客⼾端.

接⼝⽂档⼀旦写好,尽量不要轻易改变.

如若需要改变,必须要通知另⼀⽅知晓.


需求分析

加法计算器功能,对两个整数进行相加,需要客户端提供参与计算的两个数,服务端返回这两个整数的计算结果

基于以上分析,定义接口


接口定义

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

请求参数:

参数名类型是否必须备注
num1Integer参与计算的第一个数
num2Integer参与计算的第二个数

响应数据

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

2. 服务器代码

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

tip:使用 @RequestParam 限定参数为必传(方法一,其他两种在下面示例)

使用 Postman 测试:

3. 前端代码:

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

运行:

8.2 用户登录

需求:用户输入账号和密码,后端进行校验密码是否正确

  • 如果不正确,前端进行用户告知
  • 如果正确,跳转到首页,首先显示当前登录用户
  • 后续再访问首页,可以获取到登录用户信息

1. 约定前后端交互接口

对于后端而言,不涉及前端页面的展示,只需提供两个功能

  1. 登录页面:通过账号和密码,校验输入的账号密码是否正确,并告知前端
  2. 首页:告知前端当前登录用户,如果当前已有用户登录,返回登录的账号;如果没有,返回空

接口定义

(1) 校验接口

请求路径:/user /login

请求方式:POST

接口描述:校验账号密码是否正确


请求参数

参数名类型是否必传备注
userNameString校验的账号
passwordString校验的密码

响应数据

Content-Type:test 、 html

响应内容:true // 账号密码验证成功;false // 账号密码验证失败


(2) 查询登录用户接口

请求路径:/user /getLoginUser

请求方式:GET

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

请求参数:无

响应数据

Content-Type:text / html

响应内容:zhangsan(返回当前登录的用户名)

2. 服务端代码

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;@RequestMapping("/user")
@RestController
public class UserController {@RequestMapping(value = "/login", method = RequestMethod.POST)public Boolean login(String userName, String password, HttpServletRequest request) {// 方式二:
//        if (userName == null || "".equals(userName)) return false; // 使用常量.equals,避免空指针异常// 方式三:Spring 提供检测字符串是否有长度if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) return false;// 不为空,校验账号和密码是否正确if ("admin".equals(userName) && "admin".equals(password)) {// 设置 Session,为了下面获取用户名HttpSession session = request.getSession(true); // 不填也行,默认为 truesession.setAttribute("userName", userName);return true;}return false;}@RequestMapping (value = "/getLoginUser", method = RequestMethod.GET)public String getLoginUser(HttpSession session) {if (session.getAttribute("userName") != null) {return (String)session.getAttribute("userName");}return "";}
}

tip:限定必传参数的另外两种方式,上述代码中展现

Postman 测试:

3. 前端代码

ajax 是异步的,form 表单是同步的

当用户无输入时,form 表单会一直等待,输入后会跳转新页面

而 ajax 可以不跳转新页面,如下例:

当我们鼠标从账号框转移到密码框,并点击后

它会进行重复检测,这个过程肯定是在后端进行的,浏览器不能存储很大量的信息,这个检测过程并不会跳转新页面,ajax 可以实现

(1) 登录页面 login.html

对于前端而言,当点击登录按钮时,需要把用户输入的信息传递到后端进行校验,后端校验成功,则跳转到首页:index.html;后端校验失败,则直接弹窗报错

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>登录页面</title>
</head><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>function login() {// 这里使用的是 jquery 封装过的 ajax(更简洁),不是原生的// 语法:$.ajax({}); ajax 的参数是一个对象,写在 {} 里,主要内容就是完成接口文档信息$.ajax({type: "post", // 请求类型url: "/user/login", // 访问路径// 传参 key: valuedata: {"userName": $("#userName").val(), // 用 # 获取 id"password": $("#password").val()},// 返回结果success: function(body) { // 此处的 body 变量就是后端返回的 true 或 falseif (body==true) {// 跳转到 index 页面location.href = "index.html";} else {// 当前页面alert("密码错误");}}});}</script>
</body></html>

tip:页面跳转的三种方式:

  • window.location.href = "book_list.html";
  • window.location.assign("book_list.html");
  • window.location.replace("book_list.html");

以上写法,通常把 "window" 省略,比如:window.location.href = "book_list.html"; 写成 location.href = "book_list.html";

三者区别参考:location.assign()、location.href、location.replace(url)的不同

(2) 首页代码 index.html

首页代码比较简单,只显示当前登录用户即可

当前登录用户需要从后端获取,并显示到前端

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>用户登录首页</title>
</head><body>登录人: <span id="loginUser"></span><script src="js/jquery-3.7.1.min.js"></script><script>$.ajax({type: "get",url: "/user/getLoginUser",success: function(userName) {$("#loginUser").text(userName);}});</script>
</body></html>

tip:上述两个前端代码中的 jquery 存于本地

4. 运行测试

多次刷新 http://127.0.0.1:8080/index.html 发现依然可以获取到登录用户

如果重启服务器,则登录人显示为空,这是因为 Session 存在内存中,如果不做任何处理,默认服务器重启,Session 数据就丢失了

8.3 留言板

需求:

输入留言信息,点击提交,后端把数据存储起来

页面展示输入的表白墙的信息

1. 约定前后端交互接口

需求分析

后端需要提供两个服务

提交留言:用户输入留言信息后,后端需要把留言信息保存起来

展示留言:页面展示时,需要从后端后取到所有的留言信息

接口定义

(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 这样的请求,就能把当前的留言提交给服务器

2. 实现服务器端代码

定义 MessageInfo 对象
public class MessageInfo {private String from;private String to;private String message;public String getFrom() {return from;}public void setFrom(String from) {this.from = from;}public String getTo() {return to;}public void setTo(String to) {this.to = to;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}
MessageController
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
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("/getList")public List<MessageInfo> getList() {return messageInfos;}// 发布接口@RequestMapping("/publish")public String publish(@RequestBody MessageInfo messageInfo) { // 请求是 JSON 格式,参数需要加上 @RequestBody// 参数都不为空,将对象添加到 list 中if (StringUtils.hasLength(messageInfo.getFrom())&& StringUtils.hasLength(messageInfo.getTo())&& StringUtils.hasLength(messageInfo.getMessage())) {messageInfos.add(messageInfo);return "{\"ok\": 1}";}return "{\"ok\": 0}";}
}
后端测试:

成功了,但是此时的响应类型是 text 格式

需要将其转为 JSON 格式,如下:

3. lombok

3.1 介绍

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

引入依赖:

在 pom.xml 文件中的 <dependencies> </dependencies> 中添加以下语句

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
3.2 使用:

前面定义 MessageInfo 时,需要写每个参数的 get set 方法,很麻烦

lombok 可以通过注解的方式,帮我们消除一些冗长代码,使代码看起来简洁一点,如下:

import lombok.Data;@Data
public class MessageInfo {private String from;private String to;private String message;
}

@Data 注解会帮助我们自动生成一些方法

3.3 原理解释

可以观察加了 @Data 注解之后,Idea 反编译的 class 文件

这不是真正的字节码文件,而是 Idea 根据字节码进行反编译后的文件

反编译是将可执行的程序代码转换为某种形式的高级编程语言,使其具有更易读的格式

反编译是一种逆向工程,它的作用和编译器的作用相反

可以看出,lombok 是一款在编译时期生成代码的工具包

Java 程序的运行原理:

lombok 的作用如下图:

3.4 lombok 包含的方法
注解作用
@Getter自动添加 gerrer 方法
@Setter自动添加 setter 方法
@ToString自动添加 toString方法
@EqualsAndHashCode自动添加 equals 方法和 hashCode 方法
@NoArgsConstructor自动添加无参构造方法
@AllArgsConstructor自动添加全属性构造方法,顺序按照属性的定义顺序
@NonNull属性不能为 null
@RequiredArgsConstructor自动添加必须属性的构造方法,final + @NonNull 的属性为必须

@Data=@Getter+@Setter+@ToString+@EqualsAndHashCode+@RequiredArgsConstructor +@NoArgsConstructor

当我们只需要其中某种方法时,可以单独注解,如下:

4. 前端代码

<!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>// 给点击按钮注册点击事件function submit() {// 1. 获取到编辑框内容let from = $("#from").val();let to = $("#to").val();let say = $("#say").val();if (from == "" || to == "" || say == "") {alert("请检查输入内容");return;}// 检验完成,发起请求$.ajax({url: "/message/publish",type: "post",contentType: "application/json",data: {from: from,to: to,message: say},success: function (result) {if (result.ok == 1) {// 成功alert("添加成功");} else {// 失败alert("添加失败");}}});}</script>
</body></html>

测试:期望弹出一个失败或者成功的弹窗

结果并没有,而是报了两个错误

错误一:

第一个错误是因为图标问题,暂不考虑

看第二个错误,发现其状态码为 400

再看控制台给的提示:

JSON解析错误:无法识别的标记“from”:应为(JSON字符串、数字、数组、对象或标记“null”、“true”或“false”)]

看这个描述可能是 JSON 格式的问题,通过 Fiddler 抓包查看:

可以看出,正常情况下,请求应该是一个 JSON 字段,结果在浏览器请求却出现了字符串,说明问题出现在前端代码中

将 data 转为 JSON 格式

修改后重启服务:

可以添加成功,但是并没有按照我们的要求:页面展示输入的表白墙的信息

错误二:

此时,数据确实添加到后端了,但是前端并没有向后端要这些数据,页面中没有任何展示

我们希望页面在加载时就显示这些数据,因此将向后端请求数据的 ajax 写到 <script> 的最开始,如下:

重启服务,使用 Postman 构造两条数据,然后浏览器刷新显示:

出现了 undefined,肯定是刚才写的代码有问题,观察后端定义的 MessageInfo 发现:

第三个参数有问题,将其修改为 msg.message,重新启动服务:

成功了,当我们在浏览器中添加数据时:

错误三:

只显示弹窗,并没有添加数据,这是因为添加完之后并没有请求 List,没有重新加载页面,因此成功后也应该请求 List:

重启服务:

错误四:

重启服务:

添加失败了,并且浏览器的开发者工具没有错误信息,后端控制台也没有错误日志

这时我们就需要自己打印错误日志

既然页面可以弹出 添加错误 的弹窗,说明程序一定可以走到这里

通过这些日志,可以知道,result.ok 并没有获取到这个对象的属性,后端传过来的不是一个对象,而是一个字符串

这种情况下,前端也可以主动将该字符串转为 JSON 对象,如下:

重启服务,成功。

tip:两种改为 JSON 的方法(两者不能同时进行转换)

前端方法如上图

后端方法:

前端完整代码:
<!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>getList();// 将 ajax 封装到一个方法中,提高可读性function getList() {$.ajax({type: "get",url: "/message/getList",success: function (message) { // 由于后端返回的是一个 List,因此使用 for 循环打印到页面上for (let msg of message) {// 构造 html 元素let html = "<div>" + msg.from + " 对 " + msg.to + " 说: " + msg.message + "</div>";// 把构造好的元素添加进去$(".container").append(html);}}});}// 给点击按钮注册点击事件function submit() {// 1. 获取到编辑框内容let from = $("#from").val();let to = $("#to").val();let say = $("#say").val();if (from == "" || to == "" || say == "") {alert("请检查输入内容");return;}// 检验完成,发起请求$.ajax({url: "/message/publish",type: "post",contentType: "application/json",data: JSON.stringify({from: from,to: to,message: say}),success: function (result) {// 打印日志console.log(result);console.log(result.ok);// let o = JSON.parse(result);if (result.ok == 1) {// 成功alert("添加成功");// 构造 html 元素let html = "<div>" + from + " 对 " + to + " 说: " + say + "</div>";// 把构造好的元素添加进去$(".container").append(html);// 同时清理之前输入框的内容$(":text").val("");} else {// 失败alert("添加失败");}}});}</script>
</body></html>
总结:

5. 插件 EditStarter

前面插入 lombok 是通过一段 <dependency> 来插入的,以下有两种方式可以快速添加

方式一:去 maven 中央仓库查

复制上述 <dependency> 到 pom.xml 中即可

粘贴过来的代码段会有版本信息,而上面写的没有版本信息,但那时按住 Ctrl 将鼠标移到 lombok 上,显示出的版本信息也是 1.18.34

这是因为 Spring Boot 帮我们进行了版本管理

Spring Boot 不仅帮我们进行了版本管理,还帮我们处理了这些 jar 包之间的冲突

若我们有喜好的版本,也可以通过 <version> 来自己调整

方法二:EditStartes

在 pom.xml 文件中,单击右键,选择 Generate,如下图:

进入 EditStarters 的编辑界面,添加对应的依赖即可

tip:不是所有的依赖都可以在这里添加,这个界面和 Spring Boot 创建项目界面是一样的

在这里找不到的依赖,还是需要去 Maven 仓库查找坐标来添加

8.4 图书管理系统

需求:

登录:用户输入账号,密码完成登录

列表展示:展示图书

8.4.1 Bootstrap

这里使用的是版本 4

官网介绍:Bootstrap 是全球最受欢迎的前端框架,用于构建响应式、移动设备优先的网站。利用 jsDelivr 和我们提供的入门模板帮助你快速掌握 Bootstrap。

具体不展开

8.4.2 约定前后端交互接口

需求分析

图书管理系统是一个相对较大的案例,这里先实现其中一部分功能

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

账号密码校验接口:根据输入用户名和密码校验登录是否通过

图书列表:提供图书列表信息

接口定义

1. 登录接口

URL:POST /user/longin

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

响应:true // 账号密码验证成功;false // 账号密码验证失败

2. 图书列表展示

URL:POST /book/getList

请求参数:无

响应:返回图书列表,如下:

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

字段说明

id图书 ID
bookName图书名称
author作者
count数量
price定价
publish图书出版社
status图书状态 1-可借阅;其他-不可借阅
statusCN图书状态中文含义

相关文章:

Spring Web MVC 入门

1. 什么是 Spring Web MVC Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架&#xff0c;从从⼀开始就包含在Spring框架中。它的 正式名称“SpringWebMVC”来⾃其源模块的名称(Spring-webmvc)&#xff0c;但它通常被称为"Spring MVC". 什么是Servlet呢? Ser…...

吃牛羊肉的季节来了,快来看看怎么陈列与销售!

一、肉品陈列基本原则 (一&#xff09;新鲜卫生 1、保证商品在正确的温度、正确的方式下陈列 (1&#xff09;正确的温度&#xff1a;冷藏柜-2℃&#xff0d;2℃&#xff1b;冷冻柜库-20℃&#xff0d;18℃ (2&#xff09;正确的方式&#xff1a; 商品不遮挡冷气出风口&…...

租房业务全流程管理:Spring Boot系统应用

摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了租房管理系统的开发全过程。通过分析租房管理系统管理的不足&#xff0c;创建了一个计算机管理租房管理系统的方案。文章介绍了租房管理系统的系统分析部分&…...

GCC之编译(7)Linker链接脚本

GCC之(7)Linker链接脚本 Author: Once Day Date: 2024年10月25日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 本文档翻译自GNU LD链接脚本官方手册 参考文章: GNU LD …...

【设计模式系列】适配器模式(九)

目录 一、什么是适配器模式 二、适配器模式的角色 三、适配器模式的典型应用 四、适配器模式在InputStreamReader中的应用 一、什么是适配器模式 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将不兼容的接口转换为一个客户端…...

C# 文档打印详解与示例

文章目录 一、概述二、PrintDocument 类的使用三、PrintDialog 类的使用四、PageSetupDialog 类的使用五、PrintPreviewDialog 类的使用六、完整示例七、总结 在软件开发过程中&#xff0c;文档打印是一个常见的功能需求。本文将详细介绍如何在C#中实现文档打印&#xff0c;并给…...

Spring Cloud --- Sentinel 熔断规则

熔断规则 慢调用比例 发送10个请求&#xff0c;每个请求理想响应时长为200毫秒。统计1秒钟&#xff0c;如果10个请求响应时间超过200毫秒的比例大于等于10%&#xff0c;则触发熔断&#xff0c;熔断5秒。 异常比例 1秒内&#xff0c;发送请求出现异常率为20%&#xff0c;则触…...

使用爬虫爬取Python中文开发者社区基础教程的数据

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…...

你了解kafka消息队列么?

消息队列概述 一. 消息队列组件二. 消息队列通信模式2.1 点对点模式2.2 发布/订阅模式 三. 消息队列的优缺点3.1 消息队列的优点3.2 消息队列的缺点 四. 总结 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&…...

力扣102 二叉树的层序遍历 广度优先搜索

二叉树的层序遍历 题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15…...

堆(堆排序,TOP K, 优先级队列)

1 概念解释 堆的定义&#xff1a;堆是一颗完全二叉树&#xff0c;分为大堆和小堆 大堆&#xff1a;一棵树中&#xff0c;任何父亲节点都大于等于孩子的节点&#xff0c;大堆的根结点最大 小堆&#xff1a;一棵树中&#xff0c;任何父亲节点都小于等于孩子节点&#xff0c;小堆…...

(三)行为模式:11、模板模式(Template Pattern)(C++示例)

目录 1、模板模式含义 2、模板模式的UML图学习 3、模板模式的应用场景 4、模板模式的优缺点 5、C实现的实例 1、模板模式含义 模板模式&#xff08;Template Method Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了一个操作中的算法骨架&#xff0c;将某些步骤…...

贝叶斯中的充分统计量

内容来源 贝叶斯统计&#xff08;第二版&#xff09;中国统计出版社 前两篇笔记简述经典统计中的充分统计量和判断充分统计量的 N e y m a n Neyman Neyman 因子分解定理 而在贝叶斯统计中&#xff0c;充分统计量也有一个充要条件 定理兼定义 设 x ( x 1 , x 2 , ⋯ , x …...

012:ArcGIS Server 10.2安装与站点创建教程

摘要&#xff1a;本文详细介绍地理信息系统服务器软件ArcGIS Server 10.2的安装与站点创建流程。 一、软件介绍 ArcGIS Server 10.2是Esri公司开发的一款强大的地理信息系统&#xff08;GIS&#xff09;服务器软件。它支持发布和共享地图、地理数据处理服务及空间分析功能&…...

xlive.dll错误的详细解决办法步骤教程,xlive.dll基本状况介绍

在计算机的众多文件中&#xff0c;“xlive.dll”扮演着独特而重要的角色。所以当你的电脑丢失了xlive.dll文件时&#xff0c;会倒是电脑不能正常运行&#xff0c;那么出现这样的问题有什么办法可以将丢失的xlive.dll进行修复呢&#xff1f;今天这篇文章将和大家聊聊xlive.dll错…...

通俗易懂的餐厅例子来讲解JVM

餐厅版本 JVM&#xff08;Java虚拟机&#xff09;可以想象成一个虚拟的计算机&#xff0c;它能够运行Java程序。为了让你更容易理解&#xff0c;我们可以用一个餐厅的比喻来解释JVM&#xff1a; 菜单&#xff08;Java源代码&#xff09;&#xff1a; 想象一下&#xff0c;Java…...

Python从入门到高手7.3节-列表的常用操作方法

目录 7.3.1 列表常用操作方法 7.3.2 列表的添加 7.3.3 列表的查找 7.3.4 列表的修改 7.3.5 列表的删除 7.3.6 与列表有关的其它操作方法 7.3.7 与10月说再见 7.3.1 列表常用操作方法 列表类型是一种抽象数据类型&#xff0c;抽象数据类型定义了数据类型的操作方法。在本…...

Prompt提示词设计:如何让你的AI对话更智能?

Prompt设计&#xff1a;如何让你的AI对话更智能&#xff1f; 在人工智能的世界里&#xff0c;Prompt&#xff08;提示词&#xff09;就像是一把钥匙&#xff0c;能够解锁AI的潜力&#xff0c;让它更好地理解和响应你的需求。今天&#xff0c;我们就来聊聊如何通过精心设计的Pr…...

2024-10月的“冷饭热炒“--解读GUI Agent 之computer use?phone use?——多模态大语言模型的进阶之路

GUI Agent 之computer use&#xff1f;phone use?——多模态大语言模型的进阶之路 1.最新技术事件浅析三、思考和方案设计工具代码部分1.提示词2.工具类API定义&#xff0c;这里主要看computer tool就够了 总结 本文会总结概括这一应用的利弊&#xff0c;然后给出分析和工具代…...

Me 攒的GPT修改论文提示词

没有会员的GPT They demonstrated that QGAN exhibits an exponential advantage over classical methods when using data consisting of samples of measurements made on high-dimensional spaces. 作为related work 时态对吗&#xff1f; 有需要修改的吗&#xff1f;你可…...

关于在vue2中接受后端返回的二进制流并进行本地下载

后端接口返回&#xff1a; 前端需要在两个地方写代码&#xff1a; 1.封装接口处&#xff0c;responseType: blob 2.接收相应处 download() {if (this.selectionList.length 0) {this.$message.error("请选择要导出的数据&#xff01;");} else {examineruleExport…...

[BUG]warn(f“Failed to load image Python extension: {e}“)的解决办法

在使用LlaMa-Factory工具包时&#xff0c;安装好环境后&#xff0c;输入llamafactory-cli env查看llama-factory的版本等信息时&#xff0c;bash提醒&#xff1a; /home/ubuntu/anaconda3/envs/Llama-Factory/lib/python3.10/site-packages/torchvision/io/image.py:13: UserW…...

配置MUX VLAN 的实验配置

概念和工作原理: MUX VLAN&#xff08;Multiplex VLAN&#xff09;是一种高级的VLAN技术&#xff0c;它通过在交换机上实现二层流量隔离和灵活的网络资源控制&#xff0c;提供了一种更为细致的网络管理方式。 概念与工作原理 基本概念&#xff1a; MUX VLAN通过定义主VLAN&am…...

高考相关 APP 案例分享

文章首发于https://qdgithub.com/article/2032 一、核心内容 &#xff08;一&#xff09;高考相关 APP 案例 圈友朱康分享高考相关的 APP。提到猿题库&#xff0c;其主要功能有练习册和猿辅导&#xff0c;都是收费的。猿题库出题给学生练习&#xff0c;将易错的总结起来出练习…...

AI的出现对计算机相关类型的博客或论坛的影响

最近越来越感觉到&#xff0c;AI的出现对计算机相关类型的博客是一种从寄生再到蚕食的过程。 在AI没出现之前&#xff0c;大家遇到问题&#xff0c;那一般都是去百度搜索&#xff0c;然后就能找到大神前辈的解答思路&#xff0c;这些解答思路基本都是写在博客或者论坛里的&…...

[LeetCode] 784. 字母大小写全排序

题目描述&#xff1a; 给定一个字符串 s &#xff0c;通过将字符串 s 中的每个字母转变大小写&#xff0c;我们可以获得一个新的字符串。 返回 所有可能得到的字符串集合 。以 任意顺序 返回输出。 示例 1&#xff1a; 输入&#xff1a;s "a1b2" 输出&#xff1…...

大数据Azkaban(二):Azkaban简单介绍

文章目录 Azkaban简单介绍 一、Azkaban特点 二、Azkaban组成结构 三、Azkaban部署模式 1、solo-server ode&#xff08;独立服务器模式&#xff09; 2、two server mode&#xff08;双服务器模式&#xff09; 3、distributed multiple-executor mode&#xff08;分布式多…...

Vue3_开启全局websocket

1、封装websocket 新建文件夹"socket.ts"&#xff0c;路径&#xff1a;"/utils/socket" export default (onMessage: Function) > {let socketUrl ws://171.29.8.218:8080/ems/ws/screen //socket请求地址let socket: WebSocketlet lockReconnect f…...

PTA 社交集群

当你在社交网络平台注册时&#xff0c;一般总是被要求填写你的个人兴趣爱好&#xff0c;以便找到具有相同兴趣爱好的潜在的朋友。一个“社交集群”是指部分兴趣爱好相同的人的集合。你需要找出所有的社交集群。 输入格式 输入在第一行给出一个正整数 N&#xff08;≤1000&…...

USB Type-C 受电端取电快充协议芯片,支持PD+QC+FCP+SCP+AFC快充协议

前言 随着科技的飞速发展&#xff0c;电子设备对于快速充电的需求日益增加。为了满足这一需求&#xff0c;市场上涌现出了众多快充技术和产品。其中&#xff0c;XSP08Q诱骗取电芯片以其卓越的性能和广泛的应用场景&#xff0c;成为了快充领域的一颗璀璨明星。本文将对XSP08Q P…...