HTTP协议
HTTP-概述
HTTP:Hyper Text Transfer Protocol(超文本传输协议),规定了浏览器与服务器之间数据传输的规则。如果想知道http协议的数据传输格式有哪些,可以打开浏览器,点击 F12 打开开发者工具,点击Network 来查看。
浏览器和服务器是按照HTTP协议进行数据通信的。
HTTP协议又分为:请求协议和响应协议
请求协议:浏览器将数据以请求格式发送到服务器
包括:请求行、请求头 、请求体
响应协议:服务器将数据以响应格式返回给浏览器
包括:响应行 、响应头 、响应体
浏览器向服务器进行请求时,服务器按照固定的格式进行解析:
服务器向浏览器进行响应时,浏览器按照固定的格式进行解析:
学习HTTP主要是学习请求和响应数据的具体格式内容。
HTTP的特点:
1、基于TCP协议:面向连接,安全
2、基于请求-响应模型: 一次请求对应一次响应(先请求后响应)
3、HTTP协议是无状态协议: 对于数据没有记忆能力。每次请求-响应都是独立的
HTTP-请求协议
在实际应用中常用的有 :GET、POST
请求方式 | 请求说明 |
---|---|
GET | 获取资源向特定的资源发出请求。 |
POST | 传输实体主体,向指定资源提交数据进行处理请求(例:上传文件),数据被包含在请求体中。 |
GET方式的请求协议
请求行 :HTTP请求中的第一行数据。由: 请求方式 、 资源路径 、 协议/版本 组成(之间使用空格分隔)
请求方式:GET
资源路径:/brand/findAll?name=OPPO&status=1
请求路径:/brand/findAll
请求参数:name=OPPO&status=1
请求参数是以key=value形式出现
多个请求参数之间使用 & 连接
请求路径和请求参数之间使用 ? 连接
协议/版本:HTTP/1.1
.
http是个无状态的协议,所以需要在请求头设置浏览器的一些自身信息和想要响应的形式。这样服务器在收到信息后,就可以知道是谁,想干什么了。
请求头 :第二行开始,上图黄色部分内容就是请求头。格式为key: value形式
常见的HTTP请求头有:
Host: 表示请求的主机名
User-Agent: 浏览器版本。
Accept:表示浏览器能接收的资源类型,如text/*,image/*或者*/*表示所有;
Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
Accept-Encoding:表示浏览器可以支持的压缩类型,例如gzip, deflate等
Content-Type:请求主体的数据类型
Content-Length:数据主体的大小(单位:字节)
.
请求体 :存储请求参数
GET请求的请求参数在请求行中,故不需要设置请求体
POST方式的请求协议
请求行(以上图中红色部分):包含请求方式、资源路径、协议/版本
请求方式:POST
资源路径:/brand
协议/版本:HTTP/1.1
请求头(以上图中黄色部分)
请求体(以上图中绿色部分) :存储请求参数
请求体和请求头之间是有一个空行隔开(作用:用于标记请求头结束)
GET请求和POST请求的区别
区别方式 | GET请求 | POST请求 |
---|---|---|
请求参数 | 请求参数在请求行中。例:/brand/findAll?name=OPPO&status=1 | 请求参数在请求体中 |
请求参数长度 | 请求参数长度有限制(浏览器不同限制也不同) | 请求参数长度没有限制 |
安全性 | 安全性低。原因:请求参数暴露在浏览器地址栏中。 | 安全性相对高 |
HTTP-响应协议
http是个无状态的协议,所以可以在请求头和响应头中设置一些信息和想要执行的动作,这样,对方在收到信息后,就可以知道你是谁,你想干什么。
响应行(以上图中红色部分):响应数据的第一行。响应行由 协议及版本 、 响应状态码 、 状态码描述 组成
协议/版本:HTTP/1.1
响应状态码:200
状态码描述:OK
响应头(以上图中黄色部分):响应数据的第二行开始。格式为key:value形式
常见的HTTP响应头有:
Content-Type:表示该响应内容的类型,例如text/html,image/jpeg ;
Content-Length:表示该响应内容的长度(字节数);
Content-Encoding:表示该响应压缩算法,例如gzip ;
Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒 ;
Set-Cookie: 告诉浏览器为当前页面所在的域设置cookie ;
响应体(以上图中绿色部分): 响应数据的最后一部分。存储响应的数据
响应体和响应头之间有一个空行隔开(作用:用于标记响应头结束)
响应状态码
状态码 | 分类说明 |
---|---|
1xx | 响应中 ----> 临时状态码。表示请求已经接受,告诉客户端应该继续请求或者如果已经完成则忽略 |
2xx | 成功 ----> 表示请求已经被成功接收,处理已完成 |
3xx | 重定向 ----> 重定向到其它地方,让客户端再发起一个请求以完成整个处理 |
4xx | 客户端错误 ----> 处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等 |
5xx | 服务器端错误 ----> 处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等 |
关于响应状态码,先主要认识三个状态码,其余的等后期用到了再去掌握:
200 ok ----> 客户端请求成功
404 Not Found ----> 请求资源不存在
500 Internal Server Error ----> 服务端发生不可预期的错误
请求响应简介
请求响应:
请求(HttpServletRequest):获取请求数据
响应(HttpServletResponse):设置响应数据
常用的架构有BS架构和CS架构。
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。常用的架构,网页访问。
CS架构:Client/Server,客户端/服务器架构模式。需要下载APP
请求
postman
当前最为主流的开发模式:前后端分离。其中,GET请求适合用于获取资源、查询数据,而POST请求适合用于提交表单、上传文件等操作。 浏览器通过网址发起的请求全部都是GET请求;而POST请求的数据对用户来说是不可见的,如果想要测试POST请求就会很不方便,Postman应运而生。Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件,常用于进行接口测试。
简单参数
简单参数是指向服务器发起请求时,向服务器传递的是一些普通的请求数据。在 http://localhost:8080/simpleParam?name=Tom&age=10 中 name 和 age 就是简单参数。
在后端程序中,通常使用SpringBoot方式接收传递过来的普通参数数据。在Springboot的环境中,对原始的API进行了封装,接收参数的形式更加简单。简单参数,参数名与形参变量名相同,定义同名的形参即可接收参数。
@RestController
public class RequestController {
// http://localhost:8080/simpleParam?name=Tom&age=10
// 第1个请求参数: name=Tom 参数名:name,参数值:Tom
// 第2个请求参数: age=10 参数名:age , 参数值:10
//springboot方式
@RequestMapping("/simpleParam")
public String simpleParam(String name , Integer age ){ //形参名和请求参数名保持一致
System.out.println(name+" : "+age);
return "OK";
}
}
不论是GET请求还是POST请求,对于简单参数来讲,只要保证请求参数名和Controller
方法中的形参名保持一致 ,就可以获取到请求参数中的数据值。
如果在开发中,遇到请求参数名和controller方法中的形参名不相同时,可以使用Spring提供的 @RequestParam 注解完成映射。在方法形参前面加上 @RequestParam 然后通过value属性执行请求参数名,从而完成映射。注意:@RequestParam中的required属性默认为true,代表该请求参数必须传递,如果不传递将报错。
@RequestMapping("/simpleParam")
public String simpleParam(@RequestParam(name = "name", required =
false) String username, Integer age){
System.out.println(username+ ":" + age);
return "OK";
}
实体参数
在使用简单参数做为数据传递方式时,前端传递了多少个请求参数,后端controller方法中的形参就要书写多少个。如果请求参数比较多,则需要一个参数一个参数的接收,比较繁琐。此时,可以将请求参数封装到一个实体类对象中。
要想完成数据封装,需要遵守规则:请求参数名与实体类的属性名相同
简单实体对象
1、定义POJO实体类
package com.sprintbot1example.pojo;
public class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
2、Controller方法:
@RequestMapping("/simplePojo")
public String simplePojo(Userzzz sur){
System.out.println(sur);
return "ok";
}
复杂实体对象
复杂实体对象指的是,在实体类中有一个或多个属性,也是实体对象类型的。如:User类中有一个Address类型的属性(Address是一个实体类,有两个province和city属性)
复杂实体对象的封装,需要遵守如下规则:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套实体类属性参数。
1、定义POJO实体类:
Address实体类
package com.sprintbot1example.pojo;
public class Adress {
private String province;
private String city;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Adress{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
'}';
}
}
User实体类
package com.sprintbot1example.pojo;
public class Userzzz {
private String name;
private Integer age;
private Address address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "Userzzz{" +
"name='" + name + '\'' +
", age=" + age +
", address=" + address +
'}';
}
}
2、Controller方法:
@RequestMapping("/simplePojo")
public String simplePojo(Userzzz sur){
System.out.println(sur);
return "ok";
}
数组集合参数
数组集合参数的使用场景:在HTML的表单中,有一个表单项是支持多选的(复选框),可以提交选择的多个值。多个值是怎么提交的呢?其实多个值也是一个一个的提交。
后端程序接收上述多个值的方式有两种:数组 和 集合
数组
数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数。
@RequestMapping("/arrayParam")
public String arrayParam(String[] hobby){
System.out.println(Arrays.toString(hobby));
return "ok";
}
在前端请求时,有两种传递形式:
1、xxxxxxxxxx?hobby=game&hobby=java
2、xxxxxxxxxxxxx?hobby=game,java
集合
集合参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam 绑定参数关系默认情况下,请求中参数名相同的多个值,是封装到数组。如果要封装到集合,要使用@RequestParam绑定参数关系
@RequestMapping("/listParam")
public String listParam(@RequestParam List<String> hobby){
System.out.println(hobby);
return "ok";
}
日期参数
在一些特殊的需求中,可能会涉及到日期类型数据的封装。因为日期的格式多种多样(如:2022-12-12 10:05:45 、2022/12/12 10:05:45),那么对于日期类型的参数在进行封装的时候,需要通过@DateTimeFormat注解,以及其pattern属性来设置日期的格式。
Json参数
JSON是开发中最常用的前后端数据交互方式。
学习JSON格式参数,主要从以下两个方面着手:
1. Postman在发送请求时,如何传递json格式的请求参数
2. 在服务端的controller方法中,如何接收json格式的请求参数
Postman发送JSON格式数据
服务端Controller方法接收JSON格式数据
传递json格式的参数,在Controller中会使用实体类进行封装。
封装规则:JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数。需要使用@RequestBody标识。
//josn 参数
@RequestMapping("/JSONParam")
public String JSONParam(@RequestBody Userzzz userzzz){
System.out.println(userzzz);
return "OK";
}
路径参数
传统的开发中请求参数是放在请求体(POST请求)传递或跟在URL后面通过?key=value的形式传递(GET请求)。而在现在开发中,经常会直接在请求的URL中传递参数。
学习路径参数,主要掌握在后端的controller方法中,如何接收路径参数。
路径参数:
前端:通过请求URL直接传递参数
后端:使用{…}来标识该路径参数,需要使用@PathVariable获取路径参数
传递单个路径参数
// 路径
@RequestMapping("/path/{id}")
public String pathParam(@PathVariable Integer id){
System.out.println(id);
return "OK";
}
传递多个路径参数
// 路径
@RequestMapping("/path/{id}/{name}")
public String pathParam2(@PathVariable Integer id,
@PathVariable String name){
System.out.println(id+":"+name);
return "OK";
}
响应
Controller程序,除了接收请求外,还可以进行响应。controller方法中的return的结果,使用@ResponseBody注解就可以响应给浏览器。
@ResponseBody注解:
类型:方法注解、类注解
位置:书写在Controller方法上或类上
作用:将方法返回值直接响应给浏览器
如果返回值类型是实体对象/集合,将会转换为JSON格式后在响应给浏览器
但是,在所书写的Controller中,只在类上添加了RestController注解、方法添加了@RequestMapping注解,并没有使用@ResponseBody注解,怎么给浏览器响应呢?原因是:在类上添加的@RestController注解,是一个组合注解。@RestController = @Controller + @ResponseBody
在上述所编写的Controller方法中,返回值各种各样,没有任何的规范。在实际开发中,controller方法将成千上万,若controller返回值没有规范将造成整个项目难以维护。在实际项目开发中,无论是哪种方法,都会定义一个统一的返回结果。方案如下:
统一的返回结果使用类来描述,在这个结果中包含:
响应状态码:当前请求是成功,还是失败
状态码信息:给页面的提示信息
返回的数据:给前端响应的数据(字符串、对象、集合)
.
定义在一个实体类Result来包含以上信息。代码如下:
/**
* 统一响应结果封装类
*/
public class Result {
private Integer code ;//1 成功 , 0 失败
private String msg; //提示信息
private Object data; //数据 data
public Result() {
}
public Result(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Result success(Object data){
return new Result(1, "success", data);
}
public static Result success(){
return new Result(1, "success", null);
}
public static Result error(String msg){
return new Result(0, msg, null);
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
@RequestMapping("/listParam")
public Result listParam(@RequestParam List<String> hobby){
System.out.println(hobby);
return Result.success(hobby);
}
Springboot项目的静态资源(html,css,js等前端资源)默认存放目录为:classpath:/static
、 classpath:/public、 classpath:/resources
分层解耦
在进行程序设计以及程序开发时,应尽可能遵循单一职责原则,即一个类或一个方法,就只做一件事情,只管一块功能。这样可以让类、接口、方法的复杂度更低,可读性更强,扩展性更好,也更利用后期的维护。
三层架构
在项目开发中按处理逻辑,从组成上看可以分为三个部分:
数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。
逻辑处理:负责业务逻辑处理的代码。
请求处理、响应数据:负责,接收页面的请求,给页面响应数据。
按照上述对应的三个组成部分,如下:
Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
Service:业务逻辑层。处理具体的业务逻辑。
Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。
基于三层架构的程序执行流程:
1、前端发起的请求,由Controller层接收(Controller响应数据给前端)
2、Controller层调用Service层来进行逻辑处理(Service层处理完后,把处理结果返回给Controller层)
3、Serivce层调用Dao层(逻辑处理过程中需要用到的一些数据要从Dao层获取)
4、Dao层操作文件中的数据(Dao拿到的数据会返回给Service层)
用三层架构改写如下:
解耦
解耦:即解除耦合。软件开发涉及到的两个概念:内聚和耦合。内聚:指软件中各个功能模块内部的功能联系。耦合:指衡量软件中各个层/模块之间的依赖、关联的程度。
软件设计原则:高内聚低耦合。
高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 “高内聚”。
低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。
解耦思路是提供一个容器,容器中存储一些对象,controller程序从容器中获取EmpService类型的对象。要实现上述解耦操作,就涉及到Spring中的两个核心概念:
控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器。OC容器中创建、管理的对象,称之为:bean对象
依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。程序运行时需要某个资源,此时容器就为其提供这个资源。
步骤:
1.将Service层及Dao层的实现类,交给IOC容器管理
使用Spring提供的注解:@Component ,就可以实现类交给IOC容器管理
2.为Controller及Service注入运行时依赖的对象
使用Spring提供的注解:@Autowired ,就可以实现程序运行时IOC容器自动注入需要的依赖对象
Controller程序中注入依赖的Service层对象
Service程序中注入依赖的Dao层对象
IOC详解
IOC容器创建的对象称为bean对象。要把某个对象交给1OC容器管理,需要在对应的类上加上如下注解之一:
注解 | 说明 | 位置 |
---|---|---|
@Component | 声明bean的基础注解 | 不属于以下三类时,用此注解 |
@Controller | @Component的衍生注解 | 标注在控制器类上 |
@Service | @Component的衍生注解 | 标注在业务类上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上(由于与mybatis整合,用的少) |
注意事项:
声明bean的时候,可以通过value属性指定bean的名字,如果没有指定,默认为类名首字母小写。
使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
bean想要生效,还需要被组件扫描注解@ComponentScan扫描。@ComponentScan注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包。
DI详解
依赖注入,是指IOC容器要为应用程序去提供运行时所依赖的资源,而资源指的就是对象。@Autowired注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)。如果在IOC容器中,存在多个相同类型的bean对象,则会报错。Spring提供了以下几种解决方案:
使用@Primary注解:当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。
使用@Qualifier注解:指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。@Qualifier注解不能单独使用,必须配合@Autowired使用
使用@Resource注解:是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。