文章目录
1. Spring 简介
在正式学习 SpringBoot 之前,我们要先来了解下什么是 Spring。打开 Spring 的官网Spring | Home,看一下Spring的简介:Spring makes Java simple。
Spring 发展到今天已经形成了一种开发生态圈,Spring 提供了若干个子项目,每个项目用于完成特定的功能。而我们在项目开发时,一般会偏向于选择这一套 Spring 家族的技术,来解决对应领域的问题,我们称这一套技术为 Spring 全家桶。
![1.175af5b137e703c57.png](https://img-blog.csdnimg.cn/img_convert/2b3ffa3b88960c35afa410a4e066b2f7.png)
Spring 家族旗下这么多的技术,最基础、最核心的是 SpringFramework。其他的 Spring 家族的技术,都是基于 SpringFramework 的,SpringFramework 中提供很多实用功能,如:依赖注入、事务管理、web开发支持、数据访问、消息服务等等。
直接基于 SpringFramework 进行开发,存在两个问题:配置繁琐、入门难度大。
通过 SpringBoot 可以快速的帮我们构建应用程序,SpringBoot 最大的特点就是简化配置和快速开发。
2. SpringBoot 快速开始
2.1 入门程序
- 创建 SpringBoot 工程项目
基于 Spring 官方骨架,创建 SpringBoot 工程。这一步需要联网,如果创建较慢也可以使用阿里的骨架,将 Server URL 改为 http://start.aliyun.com
。
![2.106ff68341c477053.png](https://img-blog.csdnimg.cn/img_convert/7938bdcda39d9f32dcebde923e623f02.png)
基本信息描述完毕之后,勾选所需的依赖,需要什么添加什么,这里添加 Web 开发相关依赖。
![2.23e0f4744d0123112.png](https://img-blog.csdnimg.cn/img_convert/6808ed8e935b3401669a5b5c236b1642.png)
点击 Create 之后,就会联网创建这个 SpringBoot 工程。
注意:在联网创建过程中,会下载相关资源(请耐心等待)
- 定义请求处理类
在 com.baoduo 这个包下创建一个子包 controller,在 controller 包下新建一个类 HelloController,添加如下代码。
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
System.out.println("Hello World");
return "Hello World";
}
}
- 运行测试
运行 SpringBoot 自动生成的启动类 SpringBootQuickStartApplication,打开浏览器,输入 http://localhost:8080/hello
即可访问。
在 SpringBoot 项目中,静态资源(html,css,js 等前端资源)默认可以存放的目录:
- classpath:/static/
- classpath:/public/
- classpath:/resources/
- classpath:/META-INF/resources/
classpath:代表的是类路径,在 Maven 的项目中,其实指的就是 src/main/resources 或者 src/main/java,但是 Java 目录是存放 Java 代码的,所以相关的配置文件及静态资源文档,就放在 src/main/resources 下。
2.2 Web分析
浏览器:
- 输入网址:
http://192.168.100.11:8080/hello
,通过 IP 地址192.168.100.11定位到网络上的一台计算机
之前在浏览器中输入的 localhost,就是127.0.0.1(本机)
- 通过端口号8080找到计算机上运行的程序
localhost:8080
, 意思是在本地计算机中找到正在运行的8080端口的程序
-
/hello 是请求资源位置
- 资源:对计算机而言资源就是数据
- web 资源:通过网络可以访问到的资源(通常是指存放在服务器上的数据)
localhost:8080/hello
,意思是向本地计算机中的8080端口程序,获取资源位置是 /hello 的数据
8080端口程序,在服务器找 /hello 位置的资源数据,发给浏览器
服务器:(可以理解为 ServerSocket)
- 接收到浏览器发送的信息(如:/hello)
- 在服务器上找到 /hello 的资源
- 把资源发送给浏览器
网络三要素:
- IP :网络中计算机的唯一标识
- 端口 :计算机中运行程序的唯一标识
- 协议 :网络中计算机之间交互的规则
问题:浏览器和服务器两端进行数据交互,使用什么协议?
答案: HTTP 协议
2.3 起步依赖
在 SpringBoot 的项目中,有很多的起步依赖,他们有一个共同的特征:就是以spring-boot-starter-
作为开头。在刚刚的入门程序中引用的 spring-boot-starter-web
和 spring-boot-starter-test
,就是起步依赖。
以入门程序中引入的起步依赖为例:
spring-boot-starter-web
:包含了 Web 应用开发所需要的常见依赖spring-boot-starter-test
:包含了单元测试所需要的常见依赖
spring-boot-starter-web
内部把关于 Web 开发所有的依赖都已经导入并且指定了版本,只需引入 spring-boot-starter-web
依赖就可以通过依赖传递自动导入 Web 开发需要依赖
刚刚的 SpringBoot 入门程序中,并没有把程序部署到 Tomcat 的 webapps 目录下也可以运行,是因为 SpringBoot 中引入了 Web 运行环境(也就是引入 spring-boot-starter-web 起步依赖),其内部已经集成了内置的 Tomcat 服务器。
我们可以通过 IDEA 开发工具右侧的 Maven 面板中,看到当前工程引入的依赖。其中已经将 Tomcat 的相关依赖传递下来了,也就是说在 SpringBoot 中可以直接使用 Tomcat 服务器。
![2.351f412df1662b63f.png](https://img-blog.csdnimg.cn/img_convert/dadb73aeadafcbc3bb46bcd1309c4baa.png)
Spring 的官方提供了很多现成的 starter(起步依赖),我们在开发相关应用时,只需要引入对应的 starter 即可。官方地址:Spring Boot Reference Documentation
每一个起步依赖,都用于开发一个特定的功能。
举例:当我们开发中需要使用 Redis 数据库时,只需要在 SpringBoot 项目中,引入:spring-boot-starter-redis ,即可导入 Redis 开发所需要的依赖。
2.4 SpringBoot 父工程
前面的 SpringBoot 入门程序中,通过 Maven 引入的依赖,是没有指定具体的依赖版本号的。因为每一个 SpringBoot 工程,都有一个父工程。依赖的版本号,在父工程中统一管理。
![2.46955d20153d7d6db.png](https://img-blog.csdnimg.cn/img_convert/59bccba822aeb6f3dcaf244058820d7b.png)
3. 请求响应
3.1 前言
在入门程序中,我们在浏览器发起请求,请求了后端 Web 服务器(也就是内置的 Tomcat)。请求会被部署在 Tomcat 中的 Controller 接收,然后 Controller 给浏览器响应了一个字符串 “Hello World”,而在请求响应的过程中是遵循 HTTP 协议的。
其实在 Tomcat 这类 Web 服务器中,是不识别我们自己定义的 Controller 的。但前面提到 Tomcat 是一个 Servlet 容器,是支持 Serlvet 规范的,所以在 Tomcat 中可以识别 Servlet 程序。 那我们所编写的 XxxController 是如何处理请求的,又与 Servlet 之间有什么联系呢?
在 SpringBoot 进行 Web 程序开发时,它内置了一个核心的 Servlet 程序 DispatcherServlet,称之为核心控制器。 DispatcherServlet 负责接收页面发送的请求,然后根据执行的规则,将请求再转发给后面的请求处理器 Controller,请求处理器处理完请求之后,最终再由 DispatcherServlet 给浏览器响应数据。
浏览器发送请求,会携带请求数据,包括:请求行、请求头。请求到达 Tomcat 之后,Tomcat 会负责解析这些请求数据,然后将解析后的请求数据封装到 HttpServletRequest 对象中,我们可以通过 HttpServletRequest 对象获取到请求数据;对请求进行处理之后,通过 HttpServletResponse 对象可以设置响应数据, 然后 Tomcat 将响应数据响应给浏览器。
•请求(HttpServletRequest):获取请求数据
•响应(HttpServletResponse):设置响应数据
![3.195384729c68b8bab.png](https://img-blog.csdnimg.cn/img_convert/a403a98b9b8c92dc9586e26d0ef0c423.png)
上述的这种浏览器/服务器的架构模式,我们称之为:BS架构。
- BS 架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。在浏览器网页直接访问的都是 BS 架构(维护方便 体验一般)
- CS 架构:Client/Server,客户端/服务器架构模式。需要下载安装的都是 CS 架构(开发、维护麻烦 体验不错)
3.2 请求
3.2.1 Postman
在前后端分离开发模式下,后端技术人员在开发过程中,没有前端页面怎么测试自己所开发的程序?
方式1:直接使用浏览器,在浏览器中输入地址测试后端程序。
- 在浏览器地址栏中输入地址这种方式都是 GET 请求,不能使用 POST 请求
- 要使用 POST 请求,需要自己编写前端代码(比较麻烦)
方式2:使用专业的接口测试工具,比如 Postman,还有一些基于 Postman 衍生的 Apipost 和 Apifox。
Postman 是一款功能强大的网页调试与发送网页 HTTP 请求的应用,常用于进行接口测试。
Postman 原是 Chrome 浏览器的插件,可以模拟浏览器向后端服务器发起任何形式(如:get、post)的 HTTP 请求
使用 Postman 还可以在发起请求时,携带一些请求参数、请求头等信息
Download Postman | Get Started for Free直接安装
![3.2e839bd9db89be21e.png](https://img-blog.csdnimg.cn/img_convert/82ea4e87515aded67801561a63b36869.png)
3.2.2 简单参数
在原始的 Web 程序中,需要通过 Servlet 中提供的 API:HttpServletRequest
(请求对象),获取请求的相关信息。
Tomcat 接收到 HTTP 请求时:把请求的相关信息封装到
HttpServletRequest
对象中
在 Controller 中,要想获取 HttpServletRequest
对象,可以直接在方法的形参中声明 HttpServletRequest
对象。
@RestController
public class RequestController {
//http://localhost:8080/simpleParam?name=Tom&age=10
@RequestMapping("/simpleParam")
public String simpleParam(HttpServletRequest request){
String name = request.getParameter("name");//name就是请求参数名
String ageStr = request.getParameter("age");//age就是请求参数名
int age = Integer.parseInt(ageStr);//需要手动进行类型转换
System.out.println(name+" : "+age);
return "OK";
}
}
在 Springboot 的环境中,对原始的 API 进行了封装,接收参数的形式更加简单。 如果是简单参数,参数名与形参变量名相同,定义同名的形参即可接收参数。
@RestController
public class RequestController {
// http://localhost:8080/simpleParam?name=Tom&age=10
@RequestMapping("/simpleParam")
public String simpleParam(String name , Integer age ){
//形参名和请求参数名保持一致即可,SpringBoot自动完成类型转换
System.out.println(name+" : "+age);
return "OK";
}
}
不论是 GET 请求还是 POST 请求,对于简单参数来讲,只要保证请求参数名和 Controller 方法中的形参名保持一致,就可以获取到请求参数中的数据值。
如果方法形参名称与请求参数名称不一致,Controller 方法中的形参就无法接收到请求数据,如果我们开发中,需要请求参数名和 Controller 方法中的形参名不相同时,可以使用 Spring 提供的 @RequestParam
注解完成映射。代码如下:
@RestController
public class RequestController {
// http://localhost:8080/simpleParam?name=Tom&age=20
@RequestMapping("/simpleParam")
public String simpleParam(@RequestParam("name") String username , Integer age ){
System.out.println(username+" : "+age);
return "OK";
}
}
@RequestParam 中的 required 属性默认为 true,代表该请求参数必须传递,如果不传递将报错,如果该参数是可选的,可以将 required 属性设置为 false,
@RequestParam(name = "name", required = false)
3.2.3 实体参数
在使用简单参数做为数据传递方式时,前端传递了多少个请求参数,后端 Controller 方法中的形参就要书写多少个。如果请求参数比较多,通过上述的方式一个参数一个参数的接收,会比较繁琐。
此时,我们可以将请求参数封装到一个实体类对象中。 要想完成数据封装,需要遵守如下规则:请求参数名与实体类的属性名相同,注意是属性名。
定义 entity 实体类(POJO):
public class User {
private String name;
private Integer age;
getter setter toString...
}
@RestController
public class RequestController {
// http://localhost:8080/simpleEntity?name=Tom&age=10
//请求参数与实体的属性名相同
@RequestMapping("/simpleEntity")
public String simplePojo(User user){
System.out.println(user);
return "OK";
}
}
如果实体中有属性也是实体,属于复杂实体类。要遵循: 请求参数名与形参对象属性名相同,请求参数按照对象层次结构关系中间用 .
连接。
实体类:
public class User {
private String name;
private Integer age;
private Address address; //地址对象
}
public class Address {
private String province;
private String city;
}
@RestController
public class RequestController {
//http://localhost:8080/complexPojo?name=Tom&age=10&address.province=henan&address.city=zhengzhou
//请求参数使用 xxx.xxx 表示嵌套属性
@RequestMapping("/complexEntity")
public String complexPojo(User user){
System.out.println(user);
return "OK";
}
}
3.2.4 数组集合参数
在 HTML 的表单中,复选框是支持多选的,可以提交选择的多个值。其实多个值也是一个一个的提交,后端程序接收多个值可以使用数组或集合。
数组:
请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数
@RestController
public class RequestController {
//方式一:http://localhost:8080/arrayParam?hobby=game&hobby=java
//方式二:http://localhost:8080/arrayParam?hobby=game,java
@RequestMapping("/arrayParam")
public String arrayParam(String[] hobby){
//1. 形参定义为数组类型
//2. 请求参数与数组名相同
System.out.println(Arrays.toString(hobby));
return "OK";
}
}
集合:
请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam
绑定参数关系
默认情况下,请求中参数名相同的多个值,是封装到数组。如果要封装到集合,要使用
@RequestParam
绑定参数关系
@RestController
public class RequestController {
//方式一:http://localhost:8080/arrayParam?hobby=game&hobby=java
//方式二:http://localhost:8080/arrayParam?hobby=game,java
@RequestMapping("/listParam")
public String listParam(@RequestParam List<String> hobby){
//1. 使用@RequestParam注解
//2. 请求参数与集合对象名相同
System.out.println(hobby);
return "OK";
}
}
3.2.5 日期参数
日期的格式多种多样(如:2024-08-08 10:05:45 、2024/08/08 10:05:45),那么对于日期类型的参数在进行封装的时候,需要通过 @DateTimeFormat
注解,以及其 pattern 属性来设置日期的格式。
@DateTimeFormat
注解的 pattern 属性中指定了哪种日期格式,前端的日期参数就必须按照指定的格式传递。- 后端 Controller 方法中,需要使用
Date
类型或LocalDateTime
类型,来封装传递的参数。
@RestController
public class RequestController {
//http://localhost:8080/dateParam?updateTime=2024-08-08 10:05:45
@RequestMapping("/dateParam")
public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime){
//1. 使用@DateTimeFormat注解,并指定pattern
//2. 形参使用Date或LocalDateTime类型,请求参数名与形参名相同
System.out.println(updateTime);
return "OK";
}
}
3.2.6 JSON 参数
在前后端进行交互时,如果是比较复杂的参数,通常会使用 JSON 格式的数据进行传输。 (JSON是开发中最常用的前后端数据交互方式)
学习 JSON 格式参数,主要从以下两个方面着手:
- Postman 在发送请求时,如何传递 JSON 格式的请求参数
![3.34915fd7015c1215c.png](https://img-blog.csdnimg.cn/img_convert/209f0a63b8aa7ad48d97e49718398ee7.png)
- 在服务端的 Controller 方法中,如何接收 JSON 格式的请求参数
- 传递 JSON 格式的参数,在 Controller 中会使用实体类进行封装。
- 封装规则:JSON 数据键名与形参对象属性名相同,定义 entity 实体类型形参即可接收参数。需要使用
@RequestBody
标识。
public class User {
private String name;
private Integer age;
private Address address; //地址对象
}
public class Address {
private String province;
private String city;
}
@RestController
public class RequestController {
//http://localhost:8080/jsonParam 参数在请求体中
@RequestMapping("/jsonParam")
public String jsonParam(@RequestBody User user){
//1. 使用@RequestBody注解
//2. 使用实体接收参数,实体属性名与JSON键名相同
System.out.println(user);
return "OK";
}
}
3.2.7 路径参数
在开发中,经常还会直接在请求的URL中传递参数。例如:
http://localhost:8080/user/1
http://localhost:880/user/1/0
上面这种传递请求参数的形式我们称之为:路径参数。
- 前端:通过请求 URL 直接传递参数
- 后端:使用
{…}
来标识该路径参数,需要使用@PathVariable
注解获取路径参数
@RestController
public class RequestController {
//http://localhost:8080/1/ogonez
@RequestMapping("/path/{id}/{name}")
public String pathParam(@PathVariable Integer id, @PathVariable String name){
//1. 使用@PathVariable注解获取路径参数
//2. 请求路径中用{}标识路径参数
System.out.println(id+ " : " +name);
return "OK";
}
}
3.2.8 小结
- 简单参数
- 定义方法形参,请求参数名与形参变量名一致
- 如果不一致,通过
@RequestParam
手动映射
- 实体参数
- 请求参数名与实体对象的属性名一致
- 数组集合参数
- 数组:请求参数名与数组名一致
- 集合:请求参数名与集合名一致,
@RequestParam
绑定关系
- 日期参数
@DateTimeFormat
pattern
属性指定日期格式
- JSON参数
@RequestBody
,键名和对象属性名一致
- 路径参数
@PathVariable
,使用{...}
表示路径
3.3 响应
3.3.1 @ResponseBody
前面提到 HTTP 协议的交互方式:请求响应模式(有请求就有响应)
Controller 程序除了接收请求外,还可以进行响应。
在前面所编写的 Controller 方法中,都已经设置了响应数据。Controller 方法中的 return 的结果,怎么响应给浏览器呢?使用 @ResponseBody
注解:
- 类型:方法注解、类注解
- 位置:书写在 Controller 方法上或类上
- 作用:将方法返回值直接响应给浏览器
- 如果返回值类型是实体对象/集合,将会转换为 JSON 格式后再响应给浏览器
但是刚刚写的的 Controller 中,只是在类上添加了 @RestController
注解、方法添加了 @RequestMapping
注解,这是因为 @RestController
注解是一个组合注解,在类上添加 @RestController
就相当于添加了 @ResponseBody
注解,表示当前类下所有的方法返回值做为响应数据。
@RestController
= @Controller
+ @ResponseBody
//@RestController源码:
@Target({ElementType.TYPE}) //元注解(修饰注解的注解)
@Retention(RetentionPolicy.RUNTIME) //元注解
@Documented //元注解
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
3.3.2 统一响应结果
如果我们开发一个大型项目,项目中 Controller 方法将成千上万,返回值各种各样,没有任何的规范,将造成整个项目难以维护。所以我们会定义一个统一的返回结果。方案如下:
前端:只需要按照统一格式的返回结果进行解析,就可以拿到数据。
统一的返回结果使用类来描述,在这个结果中包含:
- 响应状态码
code
:当前请求是成功,还是失败 - 状态码信息
msg
:给页面的提示信息 - 返回的数据
data
:给前端响应的数据(字符串、对象、集合)
定义在一个实体类 Result 来包含以上信息。代码如下:
public class Result {
private Integer code;//响应码,1 代表成功; 0 代表失败
private String msg; //响应码 描述字符串
private Object data; //返回的数据
public Result() { }
public Result(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
getter setter...
//增删改 成功响应(不需要给前端返回数据)
public static Result success(){
return new Result(1, "success", null);
}
//查询 成功响应(把查询结果做为返回数据响应给前端)
public static Result success(Object data){
return new Result(1, "success", data);
}
//失败响应
public static Result error(String msg){
return new Result( 0, msg, null);
}
}
4. 分层解耦
4.1 前言
如果将获取数据的代码,处理数据的逻辑代码,给页面响应的代码全部都堆积在一起,写在 Controller 方法中。业务逻辑稍微复杂一点,Controller 方法的代码量就会很大。
- 当我们要修改操作数据部分的代码,需要改动 Controller
- 当我们要完善逻辑处理部分的代码,需要改动 Controller
- 当我们需要修改数据响应的代码,还是需要改动 Controller
这样就会造成我们整个工程代码的复用性比较差,而且代码难以维护。 在现在的开发中,有非常成熟的解决思路,那就是分层开发。
4.2 三层架构
在我们进行程序设计以及程序开发时,尽可能让每一个接口、类、方法的职责更单一。
单一职责原则:一个类或一个方法,就只做一件事情,只管一块功能。
这样就可以让类、接口、方法的复杂度更低,可读性更强,扩展性更好,也更利用后期的维护。
我们的程序大致上有这样三部分:
- 数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。
- 逻辑处理:负责业务逻辑处理的代码。
- 请求处理、响应数据:负责,接收页面的请求,给页面响应数据。
按照上述的三个部分,在项目开发中,我们可以将代码分为三层:
- Controller:控制层。接收前端发送的请求,与浏览器直接交互,对请求进行处理,并响应数据。
- Service:业务逻辑层。处理具体的业务逻辑。
- Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。
在传统的 Java 应用中,通常会使用 DAO(Data Access Object)模式来处理数据的访问和持久化。而在 MyBatis 等持久层框架中,通常会使用 Mapper 来实现类似的功能。DAO 和 Mapper 在目标上是相似的,都是用来处理数据的访问,但它们在具体的实现和用法上有一些区别。
- 命名和用法:
DAO 是一种设计模式,通常使用接口和实现类来定义和实现对数据的访问。在传统的 Java 应用中,DAO 接口中定义了访问数据库的方法,而 DAO 的实现类则包含了具体的数据库操作。
Mapper 是 MyBatis 中的概念,用于执行 SQL 语句并映射结果。在 MyBatis 中,Mapper 通常是一个 Java 接口,其中定义了与数据库交互的 SQL 映射语句。- SQL 映射:
DAO 通常将 SQL 语句嵌入到 DAO 实现类中,可能是直接写在代码里,也可能通过 XML 等方式配置在实现类中。
Mapper 中的 SQL 语句通常是通过 XML 文件进行配置,这样可以将 SQL 语句与 Java 代码分离,使代码更易于维护。- 框架依赖:
DAO 模式是一种通用的设计模式,可以在任何 Java 应用中使用,不依赖于特定的持久层框架。
Mapper 是 MyBatis 框架的概念,特定于 MyBatis 的用法和配置。- 数据映射:
DAO 通常返回原始的数据对象,需要手动将结果集映射到 Java 对象。
Mapper 使用 MyBatis 提供的 ResultMap 等机制,自动将结果集映射为 Java 对象。
基于三层架构的程序执行流程:
![4.1d9f0cd685397bd27.png](https://img-blog.csdnimg.cn/img_convert/e8001b1c07d7cc7e15bf671431733135.png)
- Controller 层接收前端发起的请求,调用 Service 层来进行处理(接收 Service 返回的结果之后将数据响应给前端)
- Serivce 层从 Dao 层获取数据进行逻辑处理(处理完成之后,把处理结果返回给 Controller 层)
- Dao 层操作数据库中的数据(Dao 拿到的数据返回给 Service 层)
结构如下:
![4.2bc2e6f542b119f6c.png](https://img-blog.csdnimg.cn/img_convert/384682f41264409479d989096580f7ce.png)
Controller 层中调用 Service 层的方法,先创建 Service 对象:
EmpService empService = new EmpServiceImpleA();
Service 层中调用 Dao 层的方法,先创建 Dao 对象:
EmpDao empDao = new EmpDao();
三层架构的好处:
- 复用性强
- 便于维护
- 利用扩展
4.3 分层解耦
首先需要了解软件开发涉及到的两个概念:内聚和耦合。
- 内聚:软件中各个功能模块内部的功能联系。
- 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
软件设计原则:高内聚低耦合。
高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 “高内聚”。
低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。
在刚刚的程序中,把业务类变为 EmpServiceB 时,需要修改 Controller 层中的代码,Service 的命名发生改变时,Controller 里创建的 Service 对象也要改名。Service 代码和 Controller 产生耦合。
高内聚、低耦合的目的是使程序模块的可重用性、移植性大大增强。
![4.339b4a552c25305ed.png](https://img-blog.csdnimg.cn/img_convert/f627bfab32d03e33b237663a4ea8be30.png)
之前的程序中,需要什么对象,就直接 new 一个,这种做法会使层与层之间代码耦合,所以不能 new,但是不 new 就没有对象,程序就会报错。
我们可以找一个中间人来联系它们,我们采用以下思路:
- 提供一个容器,容器中存储一些对象(例:EmpService对象)
- Controller 程序从容器中获取 EmpService 类型的对象
我们想要实现上述解耦操作,就涉及到 Spring 中的两个核心概念:
-
控制反转: Inversion Of Control,简称 IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC 容器或 Spring 容器
-
依赖注入: Dependency Injection,简称 DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入。
程序运行时需要某个资源,此时容器就为其提供这个资源。
例:EmpController 程序运行时需要 EmpService 对象,Spring 容器就为其提供并注入 EmpService 对象
IOC 容器中创建、管理的对象,称之为:bean 对象
要想实现解耦,可以将 Service 层及 Dao 层的实现类,交给 IOC 容器管理
@Component
表示当前类由 IOC 容器管理
为 Controller 程序中注入依赖的 Service 层对象,为 Service 程序中注入依赖的 Dao 层对象时依赖的对象
@Autowired
,就可以实现程序运行时 IOC 容器自动注入需要的依赖对象
@Component //将当前对象交给IOC容器管理,成为IOC容器的bean
public class EmpServiceA implements EmpService {
@Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
private EmpDao empDao ;
}
4.4 IOC
IOC 控制反转,就是将对象的控制权交给 Spring 的 IOC 容器,由 IOC 容器创建及管理对象。IOC 容器创建的对象称为 Bean 对象。
要把对象交给 IOC 容器管理,需要在类上添加一个注解:@Component
Spring 框架为了更好的标识 Web 应用程序开发中 bean 对象到底归属于哪一层,又提供了 @Component
的衍生注解:
注解 | 说明 | 位置 |
---|---|---|
@Controller | @Component的衍生注解 | 标注在控制器类上(RestController包含了) |
@Service | @Component的衍生注解 | 标注在业务类上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上(由于与mybatis整合,用的少) |
@Component | 声明bean的基础注解 | 不属于以上三类时,用此注解 |
@Service
和 @Repository
源码:
![4.46ad03ce7323e4c03.png](https://img-blog.csdnimg.cn/img_convert/de8336439b5effae6ea12f980ab87684.png)
在 IOC 容器中,每一个 Bean 都有一个属于自己的名字,可以通过注解的 value 属性指定 bean 的名字。如果没有指定,默认为类名首字母小写。
在 IDEA 中运行项目之后可以在 Actuator 中查看所有 Bean:
![4.523211cd3c228e6dd.png](https://img-blog.csdnimg.cn/img_convert/5a285f4a14e29a6c21b7e91260fda1e3.png)
使用以上四个注解都可以声明 Bean,但是在 SpringBoot 集成 Web 开发中,声明控制器 Bean 只能用
@Controller
。
使用四大注解声明的 Bean,要想生效,还需要被组件扫描注解 @ComponentScan
扫描,@ComponentScan
注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication
中,默认扫描的范围是 SpringBoot 启动类所在包及其子包。
4.5 DI
DI 依赖注入,是指 IOC 容器要为应用程序提供运行时所依赖的资源,而资源指的就是对象。
@Autowired
完成依赖注入的操作,而这个 Autowired 翻译过来叫:自动装配。
@Autowired
注解,默认是按照类型进行自动装配的(去 IOC 容器中找某个类型的对象,然后完成注入操作)
举例:在 EmpController 运行的时候,要到 IOC 容器当中去查找 EmpService 这个类型的对象,而我们通过
@Service
将 EmpService 这个类型的对象交给了 IOC 容器,所以就找到了这个类型的对象完成注入操作。
如果在 IOC 容器中,存在多个相同类型的 Bean 对象,比如上面的 EmpServiceA 和 EmpServiceB,可以使用以下方案:
@Primary
注解:设定当前类为默认的实现。
![4.6ecfd3518fdf1c226.png](https://img-blog.csdnimg.cn/img_convert/5f4d847efbdeac35a07562988ebb95ec.png)
@Qualifier
注解:指定要注入的 Bean 对象,通过 value 属性指定要注入的 Bean 的名称。不能单独使用,必须配合@Autowired
使用
![4.7e009466fa6c8329f.png](https://img-blog.csdnimg.cn/img_convert/d7d5b2a67cf370fa1b0f018610205e8b.png)
@Resource
注解:指定要注入的 Bean 对象,通过 name 属性指定要注入的 Bean 的名称。
![4.8812fa7e66481aaa4.png](https://img-blog.csdnimg.cn/img_convert/f9e3d7ac21665528e69eec9802888978.png)
@Autowird
与 @Resource
的区别
@Autowired
是 Spring 框架提供的注解,而@Resource
是 JDK 提供的注解@Autowired
默认是按照类型注入,而@Resource
是按照名称注入