SpringBoot入门
- 一、Maven项目
- 二、HTTP协议
- 2.1、协议概述
- 2.2、请求协议
- 2.3、响应协议
- 2.3、协议解析
- 三、Web服务器 -TomCat
- 3.1、Tomcat简介
- 3.2、Tomcat使用
- 3.3、内嵌Tomcat
- 四、请求响应
- 4.1、请求
- 4.1.1、postman
- 4.1.2、简单参数
- 4.1.3、实体参数
- 4.1.4、数组集合参数
- 4.1.5、日期参数与JSON参数
- 4.1.6、路径参数
- 4.1.7、请求参数总结
- 4.2、响应
- 4.2.1、@ResponseBody与统一响应
- 4.2.2、响应案例
- 4.3、分层解耦
- 4.3.1、三层架构
- 4.3.2、分层解耦
- 4.3.3、IOC & DI
- 4.3.4、IOC(控制反转)
- 4.3.5、DI(依赖注入)
一、Maven项目
- 创建Maven项目,并勾选web开发相关依赖。
- 定义HelloController类,添加方法hello,并添加注解。
- 运行测试。
这里使用2020的Idea来演示
创建Mavent项目
从File中创建一个新的模块。
选中模块中的Spring Initializr,并勾选自己的JDK版本。
下一步后修改自己的组织名字,把Type中的默认值设置为Maven,语言设置为Java,JDK版本该为自己的JDK版本。
点击下一步后来到以下的界面,勾选所需要的依赖,设置Springboot的稳定版本2.7。
基本信息到这就修改结束了,点击完成进行创建(需要网络)。
定义HelloController类,添加方法hello,并添加注解。。
完成创建后,系统会自动生成一个类。
这个类是测试类,用来启动Springboot工程。
创建一个请求处理类:HelloController,完成里面的代码。
注意:
方法中必须含有@RestController注解,不然此类就不是请求处理类。
这时候回到测试类,启动Springboot工程(运行main方法)。
找到端口号8080,并在浏览器中运行,输入localhost:8080/hello(/hello为请求路径),浏览器会返回一个Hello World。(这里用的是微软的Edge浏览器)
至此,Springboot的入门演示就此结束。
二、HTTP协议
将上节的localhost:8080/hello 网址复制到记事本中,会看到记事本中开头自动添加了 http://,
这就是http协议。
2.1、协议概述
HTTP(Hyper Text Transfer Protocol):超文本传输协议,规定了浏览器和服务器之间数据传输的规则。
如下图所示,HTTP规定了请求数据与响应数据的格式。
- HTTP的特点
- 基于TCP协议:面向连接,安全。
- 基于请求(响应模型)的:一次请求对应一次响应。
- HTTP协议是无状态的协议:对于事务处理没有记忆能力。每次请求(响应)都是独立的。
- 缺点:多次请求间不能共享数据。
- 优点:速度快。
2.2、请求协议
HTTP—请求数据格式
- 请求行:
请求数据第一行(请求方式、资源路径、协议)
- 请求头:
第二行开始,格式为:key:value的键值对方式。
- 请求体:
POST请求,存放请求参数。
注意:
请求方式有两种:GET与POST。
- GET:请求参数在请求行中,没有请求体,如:/brand/findAll?name=OPPO&status=1。(GET请求大小是有限制的)
- POST:请求参数在请求体中,POST请求大小是没有限制的。
如下图所示,绿色为请求行,蓝色为请求头。
上图中的数据含义如下表所示。
单词 | 所属含义 |
---|---|
Host | 请求的主机名 |
User - Agent | 浏览器版本,例如Chrome浏览器的标识类似Mozilla/5.0 …Chrome/79。 |
Accept | 表示浏览器能接收的资源类型,如 text/* ,image/* 或者*/* 表示所有 。 |
Accept - Language | 表示浏览器偏好的语言,服务器可以据此返回不同语言的网页。 |
Accept - Encoding | 表示浏览器可以支持的压缩类型,例如gzip,deflate等。 |
Content-Type | 请求主体的数据类型。 |
Content - Length | 请求主体的大小(单位:字节)。 |
2.3、响应协议
HTTP—请求数据格式
响应状态码的分类
状态码格式 | 所属含义 |
---|---|
1xx | 响应中—临时状态,表示请求已经接收,告诉客户端应该继续请求或者如果它已经完成则忽略它。 |
2xx | 成功—表示请求已经被成功接收,处理已完成。 |
3xx | 重定向—重定向到其他地方;让客户端再发起一次请求以完成整个处理。 |
4xx | 客户端错误—处理发生错误,责任在客户端。如:请求了不存在的资源,客户端未被授权、禁止访问等。 |
5xx | 服务器错误—处理发生错误,责任在服务器。如:程序抛出异常等。 |
常见响应状态码
最常用的三个响应状态码(☆)
状态码 | 含义 |
---|---|
200 | 客户端请求成功。 |
404 | 请求资源不存在,一般是URL输入有误,或者网站资源被删除。 |
500 | 服务器发生不可预期的错误。 |
常见响应头
响应头 | 所属含义 |
---|---|
Content-Type | 表示该响应内容的类型,例如:text/html,application/json。 |
Content-Length | 表示该响应内容的长度(字节数)。 |
Content-Encoding | 表示该响应压缩算法,例如gzip。 |
Cache-Control | 指客户端应如何缓存,例如max-age=300表示可以最多缓存300秒。 |
Set-Cookie | 告诉浏览器为当前页面所在的区域设置cookie。 |
2.3、协议解析
目前协议解析最流行的4大厂商:
- jetty://
- WebLogic
- WebSphere
- Apache Tomcat
它们把http协议解析后封装成一个服务器,供人们使用。
三、Web服务器 -TomCat
3.1、Tomcat简介
Web服务器:一个软件程序,对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是"提供网上信息浏览服务"。
Tomcat: Apache软件基金会一个核心项目,支持Servlet/JSP少量JavaEE规范,是一个开源免费的轻量级Web服务器。
-
JavaEE包含13项技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF。
-
Tomcat也被称为Web容器、Servlet容器。Servlet程序需要依赖于Tomcat才能运行。
小结:
Web服务器
- 对HTTP协议操作进行封装,简化web程序开发。
- 部署web项目,对外提供网上信息浏览服务。
Tomcat
- 一个轻量级的web服务器,支持servlet、jsp等少量javaEE规范。
- 也被称为web容器、servlet容器。
3.2、Tomcat使用
- 下载
https://tomcat.apache.org/download-90.cgi - 安装
直接解压。 - 卸载
直接删除目录即可。 - 启动
双击:bin\startup.bat - 停止
- 直接 x掉运行窗口:强制关闭。
- bin\shutdown.bat:正常关闭。
- Ctrl+C:正常关闭。
- 部署
将项目放置到webapps目录下,即部署完成。
Tomcat文件目录
安装Tomcat常见问题
- 控制台中文乱码
修改conf/logging.properties
- 启动窗口一闪而过
检查JAVA_HOME环境变量是否正确配置。
- 端口号冲突
找到对应程序,将其关闭掉(一般idea可能会占用8080)
配置Tomcat端口号(conf/server.xml)
注意:
HTTP协议默认端口号为80,如果将Tomcat端口号改为80,则将来访问Tomcat时,将不用输入端口号。
3.3、内嵌Tomcat
在Springboot中,不需要我们自己手动配置tomcat,它的里面嵌有tomcat(红色背景的依赖),可以直接使用。
起步依赖
- spring-boot-starter-web。
- spring-boot-starter-test。
内嵌Tomcat服务器
- 基于Springboot开发的web应用程序,内置了tomcat服务器,当启动类运行时,会自动启动内嵌的tomcat服务器。
四、请求响应
浏览器与后台的数据传输,是靠着前端控制器(DispatcherServlet) 来接收请求与响应的。
而前端控制器中有着两个对象:
- 请求(HttpServletRequest):获取请求数据。
- 响应(HttpServletResponse):获取响应数据
这种传输的模式被称为:BS架构。
BS架构:Browser / Server,浏览器 / 服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。
BS架构的典型应用:京东、淘宝、天猫、唯品会等。
还有一种基于客户端和服务器进行数据传输的架构叫做:CS架构。
CS架构:Client / Server,客户端 / 服务器架构模式。
CS架构的典型应用:微信、QQ等。
两种架构的优缺点
- BS架构:维护方便、体验一般。
- CS架构:开发、维护麻烦,体验较佳。
4.1、请求
4.1.1、postman
postman:一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。
作用:常用语进行接口测试。
postman的安装不需要自己手动操作,双击安装包等待即可(需要网络),注册并进入软件。
postman操作界面介绍
创建文件
-
创建工作空间
-
设置名字以及权限
- 输入网址及其保存测试文件。
总界面参数功能
4.1.2、简单参数
原始方法:在原始的web程序中,获取请求参数,需要通过HttpServletRequest对象手动获取。
创建新的测试类
在postman中测试,并加入数据
在idea中查看添加的值
这种原始方式来传递数据是非常繁琐的,而且需要手动类型转换,所以这种方式已经被淘汰了,有了新的方式:SpringBoot方式。
SpringBoot方式
简单参数:参数名与形参变量名相同,定义形参即可接收参数。
修改过后的测试类为:
得到的值依旧是Tom:10
在postman中使用post请求。
得到的结果依旧是Tom:10
如果使用post请求遇到形参与实参名不相匹的情况,idea端不会报错,只会把不匹配的参数值设置为null。
那如何避免这种情况的发生?
这时候就需要用到新的注解:@RequestParam
来防止这种情况的发生。
这个时候再去运行,就不会出现空值的情况了。
idea
但是添加这种注解还有个问题,那就是如果我没有添加user的值,那么程序又会怎么样?
可以看到,我取消了name的传递,postman里报出了400的错误,那么idea客户端呢?
idea客户端并没有出error,而是变成了黄色的waring,并且值也没有输出在console中。
出现这种警告的原因是:在@requestParam中有一个叫required的属性,默认为true,这个属性所代表的意思为:代表该请求参数必须传递,如果不传递将报错。
但是该参数是可选的,可以将required属性设置为false,这样就可以防止少穿参数而出现警告。
测试类
postman
console
这样就不会出现红色的error以及黄色的warn,但是会出现null,这是因为并没有传name的属性值。
简单参数小结
-
原始方式获取请求参数
- Controller方法形参中声明HttpServletRequest对象。
- 调用对象的getParameter(参数名)。
-
SpringBoot中接收简单参数
- 请求参数名与方法形参变量名相同。
- 会自动进行类型转换。
-
@RequestParam注解
- 方法形参名称与请求参数名称不匹配,通过该注解完成映射。
- 该注解的required属性默认是true,代表请求参数必须传递。
4.1.3、实体参数
简单实体对象:请求参数名与形参对象属性名相同,定义POJO接收即可。
测试类(封装形参,传递对象)
在postman中传参,在idea中Console显示传入的值。
复杂实体对象:请求参数与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数。
测试类参数不变,依旧是User user,User类多了个Address的参数。
User类
Address类
在postman中使用get请求,在simplePojo后加上参数:
name=Tom&age=23&address.province="北京"&address.city="北京";
注意:
这里嵌套的address对象中的值是以address.属性名来传递值的。
Console:
在Console中完整的展现出User的值。
小结:
请求参数名与形参对象属性名相同,即可直接通过POJO接收。
4.1.4、数组集合参数
数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数。
在测试类中的形参改为数组,并调用Arrays中的toString来输出数组。
在postman中添加数组内容
在Console中得到结果
集合参数:请求参数名与形参集合名称相同且请求参数为多个,@RequestParam绑定参数关系。
在测试类中修改形参为集合,并在方法中输出。
Console:
注意:
在使用集合参数时,必须在形参中添加@RequestParam
,否则在postman中会报error:500,在Console中会出现error。
数组集合参数小结
- 数组:请求参数名与形参中数组变量名相同,可以直接使用数组封装。
- 集合:请求参数名与形参中集合变量名相同,通过@RequestParam绑定参数关系。
4.1.5、日期参数与JSON参数
日期参数:使用
@DateTimeFormat
注解完成日期参数格式转换。
测试类的形参改为日期对象,添加@DateTimeFormat
注解,并设置格式(pattern = "yyyy-MM-dd HH:mm:ss")
。
在postman中使用gei方式传输数据。
JSON参数:JSON数据键名与形参属性名相同,定义POJO类型形参即可接收参数,需要使用
@RequestBody
标识符。
测试类修改成对象形参,添加@RequestBody
注解。
注意:要在postman中传递JSON格式的数据,要选择 Body 中的 raw,再在后面选择JSON格式。
4.1.6、路径参数
路径参数:通过请求URL直接传递参数,使用 {…} 来标识该路径参数,需要使用
@PathVariable
获取路径参数。
测试类中修改形参类型,使用 @PathVariable
注解,注:每一个参数前都要有一个注解。
在postman中传入参数,通过path/值1/值2
来传递。
4.1.7、请求参数总结
- 简单参数:需要通过
@RequestParam
注解来手动映射。 - 集合参数:需要通过
@RequestParam
注解来绑定关系。 - 日期参数:需要添加
DateTimeFormat
注解。 - JSON参数:需要添加
RequestBody
注解。 - 路径参数:需要添加
@PathVariable
注解。
4.2、响应
4.2.1、@ResponseBody与统一响应
@ResponseBody
- 类型:方法注解、类注解。
- 位置:Controller方法上/类上。
- 作用:将方法返回值直接响应,如果返回值类型是 实体对象 / 集合,将会转换为JSON格式响应。
- 说明: @ResController = @Controller + @ResponseBody;
现在在测试类中定义三个方法,接收与返回的值分别为:字符串、对象、集合。
当我传入的参数是hello时,那么调用的就是第一个方法,并返回Hello World。
Console
同样的,如果我传入的是listAddr,那么调用的则是第三个方法。
这样如果一个个返回新的值,调用不同特有方法,是解析繁琐,且难以维护的,所以就有了统一响应结果的方式。
统一响应结果
创建一个新的类Result,里面创建属性,分别是:
code:表示成功或者失败。
msg:提示成功或失败。
data:接收传输数据。
且在测试类中创建success的静态方法,传递数据,并且在Result类中添加get、set方法、toString方法、构造器。
这时再去改造测试类。
hello方法
getAddr方法
listAddr方法。
这三个方法都通过Result类改造完成,且返回的值都是通过Result.success()来获得,调用的方式并没有改变,这样就规范响应的统一格式,意义是:方便前端人员的维护以及修改。
@ResponseBody与统一响应结果小结
-
@ResponseBody
- 位置:Controller类 上/ 方法上。
- 作用:将方法返回值直接响应,若返回值类型是 实体对象 / 集合,转JSON格式响应。
-
统一响应结果
- Result(code、msg、data)
4.2.2、响应案例
现在的需求是:要获取员工数据,返回统一响应结果,在页面渲染展示。
完成的步骤:
- 在pom.xml文件中引入dom4j的依赖,用于解析XML文件。
- 引入资料中提供的解析XML的工具类XMLParserUtils、对应的实体类Emp、XML文件emp.xml。
- 引入资料中提供的静态页面文件,放在resource下的static目录下。
- 编写Controller程序,处理请求,响应数据。
现在开始按步骤完成
在xml文件中引入dom4j,这里引入的时候会报红,刷新mavn即可自动导入相应的包。
创建utils包,并导入一个解析XML的工具类XMLParserUtils。
在pojp包中导入Emp实体类。
在resource中导入一个xml文件。
在resource中static目录下引入前端页面。
在Controller类中编写程序,处理请求,响应数据。
package com.test.controller;import com.test.pojo.Emp;
import com.test.pojo.Result;
import com.test.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
public class HelloController {@RequestMapping("/listEmp")public Result list(){//1.加载 emp.xml 并解析 emp.xml中的数据String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();List<Emp> empList = XmlParserUtils.parse(file, Emp.class);//2.对员工信息中的gender,job字段进行处理empList.stream().forEach(emp -> {String gender = emp.getGender(); //gender: 1 男 2 女if("1".equals(gender)){emp.setGender("男");}else if ("2".equals(gender)){emp.setGender("女");}String job = emp.getJob(); //job : 1 讲师 2 班主任 3 导员if("1".equals(job)){emp.setJob("讲师");}else if("2".equals(job)){emp.setJob("班主任");}else if("3".equals(job)){emp.setJob("导员");}});//3.组装数据并返回return Result.success(empList);}
}
在postman中测试
运行成功。
注意事项:
SpringBoot项目的静态资源(html、css、js等前端资源)默认存放目录为:classpath:/static、classpath:/public、classpath:/resources。
4.3、分层解耦
在上一个案例中,Controller类集合了对xml的解析、对字段的处理、数据的返回,虽然上个案例的代码很少,也很简单,但是在以后遇到的复杂项目中会变得难以维护,复用性差,所以有了一个新的东西来优化这个问题。
4.3.1、三层架构
Controller类中的代码可以分成如下的三个模块:
每个模块的职责单一,分别处理不同的需求,这样的优势就是不用进行全局修改,可以局部调整,而不去影响其他的模块。
三层架构
- controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。
- service:业务逻辑层,处理具体的业务逻辑。
- dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查。
通过三层架构,可以清晰的看到,前后端的交互是:
请求
Controller层接收由浏览器传输过来的请求,再传输给Service层,经过Service层的逻辑处理后再调用dao层,dao层再对文件进行操作(增删改查)。
响应
请求的路径结束完后,由dao层拿到对文件操作后的数据,返回给Service层进行逻辑处理,再返回给Controller,最后由Controller响应数据给前端的浏览器。
这样做的优点是:
假如我要实现业务逻辑的改变,就可以单独修改Service层的逻辑即可,并不需要修改Controller层与dao层的数据,这样项目的可维护性更高。
使用三层架构来改进代码
Controller层
HelloController类
@RestController
public class HelloController {private EmpService empService = new EmpServiceA();@RequestMapping("/listEmp")public Result list(){//1.调用service,获取数据List<Emp> empList = empService.listEmp();//3.组装数据并返回return Result.success(empList);}
}
Service层
EmpService接口
public interface EmpService {//获取员工列表数据public List<Emp> listEmp();
}
EmpServiceA类
public class EmpServiceA implements EmpService {private EmpDao empDao = new EmpDaoA();@Overridepublic List<Emp> listEmp() {//1.调用dao,获取数据List<Emp> empList = empDao.listEmp();//2.对员工信息中的gender,job字段进行处理empList.stream().forEach(emp -> {String gender = emp.getGender(); //gender: 1 男 2 女if("1".equals(gender)){emp.setGender("男");}else if ("2".equals(gender)){emp.setGender("女");}String job = emp.getJob(); //job : 1 讲师 2 班主任 3 导员if("1".equals(job)){emp.setJob("讲师");}else if("2".equals(job)){emp.setJob("班主任");}else if("3".equals(job)){emp.setJob("导员");}});return empList;}
}
dao层
EmpDao接口
public interface EmpDao {//获取员工数据列表public List<Emp> listEmp();
}
EmpDaoA类
public class EmpDaoA implements EmpDao {@Overridepublic List<Emp> listEmp() {//1.加载 emp.xml 并解析 emp.xml中的数据String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();List<Emp> empList = XmlParserUtils.parse(file, Emp.class);return empList;}
}
4.3.2、分层解耦
- 内聚:软件中各个功能模块内部的功能联系。
- 耦合:衡量软件中各个层 / 模块之间的依赖、关联的程度。
- 软件设计原则:高内聚,低耦合。
从上小节的代码可以看到,三层结构之间都互有联系,即使修改了一个层级中的机构,还是有可能要修改其他层级的代码,为了防止这种耦合的发生,我们可以在每个层级之间添加一个容器,来作为中间层级,降低它们的耦合关系。
这时候接触了新的三个概念:
- 控制反转(IOC):Spring框架的第一大核心。对象的控制权由程序自身转移到外部(容器),这种思想称为控制反转。
- 依赖注入(DI):容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
- Bean对象(bean):IOC容器中创建、管理的对象,称之为bean。
4.3.3、IOC & DI
IOC & DI 快速入门
步骤:
- Service层 及 Dao层的实现类,交给IOC容器管理。
- 为Controller 及 Service注入运行时,依赖的对象。
- 运行测试。
如何把Service层 和 Dao层的实现类交给IOC容器?
在实现类上加上@Component注解就可以实现。
如何为Controller 及 Service 注入依赖对象?
在它们的开头加上@Autowired注解就可以实现,在运行时,IOC容器会提供该类型的bean对象,并赋值给该变量(依赖注入)。
- Service层 及 Dao层的实现类,交给IOC容器管理。
- 为Controller 及 Service注入运行时,依赖的对象。
测试
4.3.4、IOC(控制反转)
控制反转:指将对象的控制权交给IOC容器,由IOC容器来创建和管理这些对象,IOC里的这些对象也被称为Bean对象。
Bean的声明
想要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:
注意事项:
- 声明bean的时候,可以通过value属性指定bean的名字,如果没有指定,默认为类名首字母小写。
- 使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
Bean组件扫描
- 前面声明bean的四大注解,要想生效,还需要被组件扫描注解:@ComponentScan 扫描。
- @ComponentScan 注解虽然没有显示配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中,默认扫描的范围是启动类所在包及其子包。
ComponentScan({需要扫描的包},{启动类所在包})
小结:
注解的声明
- @Component,@Controller,@Service,@Repository
- @SpringBootApplication具有包扫描作用,默认扫描当前包及其子包。
4.3.5、DI(依赖注入)
DI:IOC容器要为应用程序提供运行时所依赖的资源。资源,指的是对象。
Bean注入
- @Autowired注解,默认是按照类型进行,如果存在多个相同类型的bean,将会报出如下错误:
上面出现的错误可以通过以下几种方案来解决:
- @Primary (设置bean优先级,想用哪个就在哪个上面添加)
- @Qualifier(@Autowired 配合 @Qualifier 指定注入的是哪个bean)
- @Resource(指定注入哪个bean,指定bean的名字)
@Primary
@Primary
@Service
public class EmpServiceA implements EmpService{}
@Qualifier
@RestController
public class EmpController{@Autowired@Qualifier("empServiceA")private EmpService empService;
}
@Resoure
@RestController
public class EmpController{@Resource(name = "empServiceB")private EmpService empService;
}
小结:
依赖注入的注解
- @Autowired:默认按照类型自动装配。
- 如果同类型的bean存在多个:
- @Primary
- @Autowired + @Qualifie(“bean的名称”)
- @Resource(name = “bean的名称”)
@Resource 与 @Autowired区别
- @Autowired 是 spring框架提供的注解,而@Resource是JDK提供的注解。
- @Autowired 默认是按照类型注入,而@Resource默认是按照名称注入。