SpringBoot解析
SpringBoot起步依赖
SpringBoot在配置上相比Spring要简单许多,其核心在于starter起步依赖。在使用SpringBoot来搭建一个项目时,只需要引入官方提供的starter起步依赖就可以直接使用,免去了各种配置。因为起步依赖可以引入某个功能的相关依赖和一些初始化的配置
-
web依赖
spring-boot-starter-web:内部把Web开发的必要依赖一次性导入并且还指定了版本,只需要导入一个web起步依赖,就可以实现Web开发
**注意:**web起步依赖内置Tomcat,这样就非常方便部署和启动SpringBoot项目,默认端口号是8080
- test依赖
spring-boot-starter-test:内部包含了SpringBoot测试(对单元测试的封装)所需要的必须依赖
SpringBoot项目版本管理
SpringBoot使用了Maven的继承,每一个SpringBoot工程都有一个父工程,依赖的版本号,在父工程中统一管理。
也就是说SpringBoot项目只需要继承spring-boot-starter-parent,引入依赖就无需指定版本,这样提高了开发效率,有不用担心会出现版本冲突问题。版本号由SpringBoot统一指定
-
spring-boot-starter-parent
在这个父pom文件中可以看到插件的配置、资源的声明以及Java版本、编码格式等信息
**注意:**SpringBoot最低支持JDK1.8,编码格式为UTF-8
-
spring-boot-dependencies
在spring-boot-starter-parent文件中,也有一个parent父依赖,在此父pom中可以看到所有依赖的版本定义,以及dependencyManagement节点,这就是项目依赖可以省略版本version的原因
-
Maven管理标签
parent:继承标签,Mavan父子项目中用于继承父项目
dependencyManagement:依赖管理标签,用于在父项目声明依赖,但并不是引入依赖到项目中。在子工程中用到声明的依赖时,可以不加依赖的版本号,这样可以做到统一管理工程中用到的依赖版本
pluginManagement:插件管理标签,作用与dependencyManagement一样,也是声明插件但不引入。在子工程中用到声明的插件时,可以不加插件的版本号,这样可以做到统一管理工程中用到的插件版本
properties:属性标签,在标签内可以把版本号作为变量进行声明,后面dependency中用到版本号时可以用${变量名}的形式代替,这样做的好处在于当版本号发生改变时,只要更新properties标签中的变量就行了,不用更新所有依赖的版本号
dependencies:依赖标签,项目中要引入的依赖需要通过此标签进行引入
build:描述了如何编译及打包项目,具体的编译和打包工作是通过其中的plugin配置来实现的,Maven的所有操作其根本都是通过插件进行实现的
SpringBoot启动类
当一个类使用**@SpringBootApplication**这个注解他就是SpringBoot启动类,SpringBoot就是运行这个类的main方法来启动Spring程序的
启动类位置:启动类会扫描当前包及其子包的相关注解,这些注解需要被启动类扫描到才会起作用。所以启动类需要放在业务类同包或父包中,也就是保证启动类是所有Java代码的平级或父级
启动类命名规则:
- 项目名称+Application
- 项目名称+App
SpringBoot配置文件
SpringBoot的配置文件有三种,优先级最高的是properties,如果内容发生冲突以properties为主,如果内容不冲突这三个配置文件的所有配置都会生效
- application.properties
- application.yaml
- application.yml
SpringBoot的配置文件存放位置就在src/main/resources中
-
properties写法
properties配置文件是以key=value的方式编写配置,编写比较繁杂不建议使用
-
Yaml/Yml写法
-
yml语法
yaml和yml在写法上是一致的,只不过通常我们使用简写yml作为文件后缀。yaml/yml编写语法极其严格,如果编写错了会有警告,请时常注意右上角的提示
1. key和value之间除了有冒号在冒号后面还必须有空格:key: value 2. 配置前缀层级关系只能以一个Tab的空格,不能多也不能少 3. value如果是字符串不需要加双引号 注意:yml中编写错误右上角会有提示-显示黄色警告(正确时显示绿色√),但有的时候是误报要注意区分
-
多环境配置
编写多套环境配置在不同环境中我们可以进行快速切换,而不需要再去修改原有配置,更加高效。把什么是多环境配置呢?就是在项目中编写多套环境
- dev:开发环境,端口号为8081
- pro:生产环境,端口号为8082
- test:测试环境,端口号为8083
-
方式一:所以配置写在一个文件中
# 最上面的就是默认配置,默认配置不需要指定profile
server:
# 设置端口号
port: 8081
spring:
profiles:
# 要启动的环境标识,如果环境标识在下面找不到那么就启动默认配置
active: dev
# 每套环境中间需要有此分隔符,分隔每套环境
---
server:
# 设置端口号
port: 8082
spring:
config:
activate:
on-profile: pro
---
server:
# 设置端口号
port: 8083
spring:
config:
activate:
on-profile: test
方式二:多文件配置
- 不同的环境写在不同的Yml文件中,在主yml中进行引用
- 环境配置文件命名规则:application-{profile}.yml
SpringBoot单元测试
-
概述:
使用骨架搭建一个SpringBoot程序时,会自动生成一个测试类,这个测试类就是SpringBoot测试类
-
详解
-
导入依赖
想要使用SpringBoot测试类那么必须导入spring-boot-starter-test依赖,它是一个起步依赖,会把单元测试的相关依赖一次性导入
-
@SpringBootTest注解
此注解用于申明当前类是SpringBoot测试类,用于测试SpringBoot的相关代码
-
Junit5
SpringBoot2.2.0版本之后替换成了Junit Jupiter,所以可以看到依赖变成了Junit Jupiter包下
-
特性 | JUnit4 | JUnit5 |
---|---|---|
声明一个测试方法 | @Test | @Test |
在当前类的所有测试方法执行前要执行的方法 | @BeforeClass | @BeforeAll |
在当前类的所有测试方法执行后要执行的方法 | @AfterClass | @AfterAll |
每个测试方法执行前要执行的方法 | @Before | @BeforeEach |
每个测试方法执行后要执行的方法 | @After | @AfterEach |
忽略某个测试方法或测试类 | @Ignore | @Disabled |
动态测试用例生成工厂 | 无此特性 | @TestFactory |
嵌套测试 | 无此特性 | @Nested |
标记与过滤 | @Category | @Tag |
注册定制扩展点 | 无此特性 | @ExtendWith |
项目开发模式
-
前后端分离
目前主流的开发模式就是前后端分离模式,前端项目和后端项目分开构建,分开开发和部署。前端使用Mockjs或fastMock模拟假数据和接口进行测试,后端使用PostMan、ApiFox等工具进行测试
Controller控制器
什么是控制器
JavaWeb中有一个技术专门用来处理前端的请求,那就是Servlet。
Tomcat既是一个web服务器,又是一个Servlet容器,所以Tomcat可以用来处理Servlet程序
Controller是一个代码方案,可以通过具体的技术实现:Servlet,SpringMvc。Servlet是处理请求的底层代码,而SpringMvc是Spring提供的一个处理请求的框架,底层还是通过Servlet实现的,可以看成是对Servlet的封装,让代码写起来更加简单,更加灵活
案例中的Controller,就是用SpringMvc框架实现的,底层是Servlet,可以运行在Tomcat服务器中
请求流程:浏览器发起请求到后端SpringBoot内置的Tomcat服务器,请求会被部署在Tomcat中的Controller控制器类处理,然后匹配请求执行对应的方法
SpringMvc
SpringMvc概述
SpringMvc是基于Spring框架的一个用于处理web请求的框架,底层就是Servlet
- SpringBoot中已经封装好了SpringMvc框架,我们只需要导入起步依赖spring-boot-starter-web即可使用
- SpringMvc中有一个非常重要的Servlet类,叫做DispatcherServlet,这也是SpringMvc的核心功能
- Spring(ioc,aop - 管理对象,抽取公共业务)、SpringMvc(处理请求的)、SpringBoot(简化配置,简化启动部署)关系如下
DispatcherServlet
SpringMVC提供了一个DispatcherServlet类,称之为核心控制器。DispatcherServlet负责接收请求,然后根匹配规则将请求再转发给后面的请求处理器Controller,请求处理器处理完请求之后,最终再由DispatcherServlet给浏览器响应数据
请求和响应对象
浏览器发送请求,会将请求包装成请求数据包发送给tomcat服务器,Tomcat会解析这些请求数据,然后将解析后的请求数据传递给HttpServletRequest对象,那也就意味着 HttpServletRequest对象就可以获取到请求数据,例如:请求参数、请求IP、请求头信息等等
Tomcat还给Servlet程序传递了一个参数 HttpServletResponse,通过这个对象,我们就可以给浏览器设置响应数据
后端接口
控制器类中的方法通常而言我们俗称接口,此接口并非Java中定义的接口,是两个不同的东西。前端人员和测试人员都称之为接口。以后我们后端人员调用第三方的也称之为调用接口。我们入门案例中的Controller方法就是一个接口,那个Controller类就是接口类。例如:
- 前端人员:你把SpringBoot入门接口地址给我一下
- 后端人员:好的,http://localhost:8080/hello
接口也被称为一个资源:请求后端接口也可以是请求后端资源/访问资源
API协同研发工具
-
API协同开发主流工具
早年使用Postman,近几年来国内逐渐出现了多款优秀的API协同研发工具,例如:ApiPost、ApiFox。
-
Apifox
Apifox官网:https://apifox.com/
数据交换格式
-
前后端(不同的语言)交互复杂数据发送的数据格式应该是什么样子?
-
不同项目之间要交互数据,数据格式应该是什么样子?
-
不同开发语言之间要交互数据,数据格式应该是什么样子?
答:统一使用的数据格式进行交互
-
什么是数据交换格式
数据交换/数据交互指的是不同语言或不同项目之间数据的共享,数据要以统一的格式(所有语言,所有项目都能识别)进行保存。而目前基本所有项目或所有语言都能识别的数据格式:xml和json
xml一般只用于做配置文件。而json以轻量和解析方便的优势作为数据交换格式
-
Json数据格式
什么是Json:JavaScript Object Notation - JS对象表示法,是一种轻量级的数据交换格式。易于人阅读和编写,可以在多种语言之间进行数据交换,同时也易于机器解析和生成。是现在最主流的数据交换格式。以后我们前后端交互使用最多的就是使用Json格式进行数据交互
Json数据格式
- 一个对象:{“key1”:“value1”,“key2”:“value2”…}
- 多个对象:[{“key1”:“value1”,“key2”:“value2”…},{“key1”:“value1”,“key2”:“value2”…},{“key1”:“value1”,“key2”:“value2”…}]
注意:JSON数据格式就是以key-value形式的数据格式,KEY必须 以 “” 包裹起来,并且如果value是字符串也必须以 “” 包裹起来,如果不是字符串那么正常写就可以
//JSON表示一个对象:
{"username":"张三","age":16,"sex":true,"dept":{"id":001,"name":"开发部"}}
//JSON表示多个对象:
[{"username":"张三","age":16,"sex":true,"dept":{"id":001,"name":"开发部"}},
{"username":"李四","age":26,"sex":false,"dept":{"id":003,"name":"测试部"}},
{"username":"王五","age":36,"sex":true,"dept":{"id":002,"name":"运维部"}}]
SpringBoot中Controller应用
1. 公共资源路径
在企业开发中,不会把所有模块的接口写在一个控制器类中,通常会按照模块划分控制器类.,那么将模块划分到了不同的控制器类中之后,通常会为每个模块的控制器类定义一个唯一公共资源标识
例如:请求用户模块:/user
请求员工模块:/employee
抽取公共资源路径:将公共资源路径抽取到控制器类上使用@RequestMapping("公共资源路径")注解标识
2. 参数接收
2.1. 请求对象接收
在控制器方法中,我们可以通过HttpServletRequest对象来接收前端传递的数据,不管是普通参数、对象参数、集合参数等等
这种方式大家作为了解即可,现在企业开发中已经不再使用这种方式进行参数接收了,所以学习普通参数获取即可,其他参数获取方式一致
2.2. 参数接收-通过参数名
1.2.1 参数名一致
通过参数名接收就是直接在控制器类对应方法中使用参数名,参数名称必须跟前端传递的一致。参数类型会自动进行转换,也就是说前端传递的字符串后端可以用非字符串类型接收
需要注意的是:参数类型不要用基本数据类型,容易报错400
1.2.2 参数名不一致
当参数名称不一致时,后端就无法通过名称识别到对应参数,解决方案就是在形参前面加上@RequestParam(value=“传递参数名称”)指定前端传递的那个参数映射到形参中
@RequestParam属性
- value:指定前端传递的参数名称
- required:此参数是否必填,默认为true,如果使用默认为true的属性值前端没有传递此参数时会响应400
2.3 参数接收-实体对象
虽然可以使用参数名一个一个接收,但是如果参数个数比较多那么这种方式就会非常麻烦。当参数比较多时我们可以选择使用实体参数进行接收,实体类字段名称必须跟前端传递的参数名称一致,否则获取为null值
2.4 参数接收-数组/集合
默认情况下,请求中参数名相同的多个值,是封装到数组。如果要封装到集合,要使用@RequestParam绑定参数关系
2.5 参数接收-日期参数
日期类型与其他类型不同,日期类型在接收时需要指定@DateTimeFormat(pattren=“日期格式”)注解,然后通过Date或LocalDateTime对象进行接收。请求时日期格式必须和后端注解中指定的格式保持一致
2.6 参数接收-JSON数据
前面学习JSON数据格式时我们知道以后前后端交互复杂数据时通常都是使用JSON
- 如果是简单的的参数那么使用前面学习的形参方式进行接收即可
- 如果是简单对象或者复杂对象那么通常都会使用JSON进行接收
前端想要传递JSON数据格式那么必须将数据放入到请求体中,后端需要从请求体中获取数据映射到对象中,所以要求请求方式需要用POST请求
注意:浏览器只能发GET请求,设置参数到请求体建议使用Post请求,所以使用JSON参数时请使用Postman进行测试
**注意:**后端想要从请求体中获取到数据映射到对象中,那么必须在参数前面加上注解@RequestBody
2.7. 参数接收-路径参数
目前企业开发中比较流行从路径中获取参数
例如:下面这两个请求中地址中,数字可以作为请求路径也可以是参数
- http://localhost:8083/user/1
- http://localhost:8083/user/1/2
后端接收:在路径中使用{…}来标识该路径参数,形参前面需要使用**@PathVariable(“{}中的名称”)**注解来获取路径参数
3. RestFulAPI
3.1. RestFulAPI概述
REST:Resource Representational State Transfer的缩写。意思是:“表现层状态转移”
RESTfulAPI:RESTFul风格的接口。用URL定位资源,用HTTP动词(GET、POST、PUT、DELETE)描述操作
凡是满足REST的约束规范的应用均可称为Restful应用。RESTful规范:
- 请求方式区别资源
- 资源路径只能有名词不能有动词
**注意:**同一个Controller类/接口类中不能有相同请求(请求方式 + 请求地址 ),否则启动就报错
操作 | 请求方式 | 说明 |
---|---|---|
C:新增 | POST | 新增操作使用POST请求方式 |
R:读取 | GET | 获取等操作使用GET请求方式 |
U:修改 | PUT | 修改等操作使用PUT请求方式 |
D:删除 | DELETE | 删除等操作使用DELETE请求方式 |
- 传统请求方式:会在路径中体现要做什么操作
请求地址 | 操作说明 |
---|---|
http://localhost:8080/user/getUsers | 查询所有用户 |
http://localhost:8080/user/getUser?id=1 | 查询单个用户 |
http://localhost:8080/user/addUser | 新增用户 |
http://localhost:8080/user/updateUser | 修改用户 |
http://localhost:8080/user/deleteUser?id=1 | 删除单个用户 |
- RESTfulAPI:以请求方式来区别做什么操作
请求地址 | 请求方式 | 操作说明 |
---|---|---|
http://localhost:8080/user | GET | 查询所有用户 |
http://localhost:8080/user/{id} | GET | 根据单个用户 |
http://localhost:8080/user | POST | 新增用户 |
http://localhost:8080/user | PUT | 修改用户 |
http://localhost:8080/user/{id} | DELETE | 删除单个用户 |
3.2. RestFulAPI使用
@RequestMapping注解可以指定所有请求方式,企业开发中不再使用@RequestMapping指定接口请求方式,只使用@RequestMapping注解指定唯一公共资源路径,而是直接使用请求方式对应的注解:所有的请求方式都有自己对应的注解
- @XxxMapping:使用不同请求方式的Mapping注解,代表使用不同的请求方式
3.3. 参数响应
企业开发中,前端发起了请求那么后端接口就会提供响应,并且通常我们会响应JSON格式数据给到前端。后端想要响应JSON数据那么就必须在接口上加上一个注解@ResponseBody,表示要将响应数据转换为JSON格式数据响应给前端。这也是前面说的前后端交互使用JSON格式
但是在前面编写接口时我们并没有使用@ResponseBody注解加在接口方法上,我们响应的字符串也响应给了前端,这是如何做到的?
原因就在于类上的@RestController注解,这是一个复合注解,其底层包含了如下两个注解
- @Controller:标识当前类是一个控制器类,后续详细讲解
- @ResponseBody:加在类上代表整个控制器类的接口方法都响应JSON格式数据返回给前端
注意:使用@ResponseBody注解将数据转换为JSON格式是必须返回值是可以进行转换的,如果转换不了那么会直接原样展示给前端,例如前面响应一个字符串那么就无法转换为JSON格式,因为JSON格式必须是key:value结构
3.3.1. 响应json数据演示
- 接口响应单个JSON对象
- 接口响应集合JSON对象
3.3.2. 统一响应结果
前面我们响应的返回值类型是根据我们具体要响应的类型决定,在企业开发中通常会统一响应结果
为什么要统一:如果不统一响应结果那么前端解析响应结果时,那么问题如下
- 前端每次都要根据不同的结果做不同的处理,非常麻烦
- 不知道后端接口请求是否成功,无法做进一步的处理
最好的方式:响应统一的数据结构,前端知道是否成功、请求状态码、响应提示语、数据。做统一处理
定义统一响应结果类工具类:
package cn.itsource.utils;
/**
* @Date: 2023-08-04
* @Description: 响应信息结果类
* @Version:1.0
*/
public class R<T> {
// 响应状态码
private Integer code;
// 操作结果
private Boolean success;
// 响应数据 - 描述信息
private String message;
// 响应数据 - 其他数据
private T data;
public static <T> R<T> ok() {
return new R<T>(200, true, "操作成功", null);
}
public static <T> R<T> ok(T data) {
return new R<T>(200, true, "操作成功", data);
}
public static <T> R<T> ok(String message) {
return new R<T>(200, true, message, null);
}
public static <T> R<T> ok(String message, T data) {
return new R<T>(200, true, message, data);
}
public static <T> R<T> fail() {
return new R<T>(500, false, "操作失败", null);
}
public static <T> R<T> fail(T data) {
return new R<T>(500, false, "操作失败", data);
}
public static <T> R<T> fail(String message) {
return new R<T>(500, false, message, null);
}
public static <T> R<T> fail(Integer code) {
return new R<T>(code, false, "操作失败", null);
}
public static <T> R<T> fail(String message, T data) {
return new R<T>(500, false, message, data);
}
public static <T> R<T> fail(Integer code, T data) {
return new R<T>(code, false, "操作失败", data);
}
public static <T> R<T> fail(Integer code, String message) {
return new R<T>(code, false, message, null);
}
public static <T> R<T> fail(Integer code, String message, T data) {
return new R<T>(code, false, message, data);
}
public R(Integer code, Boolean success, String message, T data) {
this.code = code;
this.message = message;
this.success = success;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
综合案例
使用Spring+SpringMVC+SpringBoot做一个综合案例
写一个UserController,并使用ResultFul风格的api编写接口,实现User的CRUD5个接口方法,要求返回json数据统一【R】,使用Apifox发送请求模拟数据的增删改查
(页面过多),详情参见我的csdn
三层架构
如果所有代码都放在Controller中是不合理的,就会导致以下问题:
| 复用性差 | 耦合度高 | 可读性差 | 难以维护 | 扩展性低 | 可测试性低
在企业级开发中,通常使用分层开发解决上述问题,也就是使用三层架构
1. 三层架构概述
在进行程序设计以及程序开发时,应该遵守单一职责原则,意思就是尽可能让每个接口、类、方法的职责更单一
单一职责原则
- 大到模块,小到方法,应该只包含单一的职责,只负责一件事情
- 单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构手法中都能找到它的存在,它是最简单但又最难运用的原则
在综合案例中我们开发的程序并没有满足单一职责原则,可以将综合案例中的代码可以分为三个部分
- 数据访问:负责业务数据的维护操作,包括增、删、改、查等操作 - Dao层/数据访问层:操作数据
- 逻辑处理:负责业务逻辑处理的代码 - Service层/业务层:实现业务
- 请求处理和响应数据:负责接收页面的请求,给页面响应数据 - 表现层中的Controller:与用户交互
三层架构程序执行流程
- 浏览器发送请求到Controller
- Controller调用Service层代码进行业务处理
- Service层调用Dao层进行数据处理,并响应数据给Controller层
- Dao层对数据进行增删改查等操作,并响应数据给Service层
2. 三层架构命名规范
三层 | 包名 | 类名 |
---|---|---|
Controller | controller | XxxController |
Service | Service层包名:service Service层实现类包名:service.impl | 接口:IXxxService 实现类:impl.XxxServiceImpl |
Dao( Data Access Object ) | Dao层包名:dao Dao层实现类包名:dao.impl | 接口:IXxxDao 实现类:impl.XxxDaoImpl |
Spring框架
为了提高开发效率,简化配置和启动部署,我们使用了SpringBoot这个框架
昨天我们又学习了SpringMvc框架,我们写的Controller就是用SpringMvc框架实现的,主要用于处理请求的
今天我们来学习Spring框架,它是一个开源的容器框架,提供了IOC和AOP两大核心机制。IOC控制反转主要用于创建对象和对象的依赖关系,AOP面向切面编程主要用于抽取公共业务,解耦代码降低耦合度
1. 高内聚低耦合
高内聚和低耦合是软件工程中的核心概念,是判断软件设计好坏的标准,它们分别从不同的角度描述了软件系统的模块或组件之间的关系
高内聚:表示一个模块或组件内部各个元素之间存在着紧密的联系和协作,共同完成特定的、单一的任务。这样的模块或组件拥有较高的功能独立性,便于维护和复用
相当于我不能缺你,缺了你就不能正常运行了
例如:我们抽取的Student模块,各层只编写了和学生相关的代码,那么这就是高内聚的表现
低耦合:则意味着模块或组件之间的依赖关系较少,它们的接口简单且清晰,这有助于提高系统的可重用性、可维护性和可扩展性。低耦合的系统更容易进行模块化开发和组装
相当于我可以缺你,缺你影响也不是很大,用另外一个产品也能替换你
2. 解耦思想
虽然我们将之前在Controller写的所有代码以三层架构的代码方式实现了,实现了高内聚,降低了耦合度,提高代码的复用性、可读性和可维护性,但是接口和实现类在同一个地方出现就是任然是一个高度耦合代码
IStudentService service = new StudentServiceImpl();
IStudentDao dao = new StudentDaoImpl();
//一个接口可以有多个实现类,假如实现类换了一种实现,那么这个代码就必须要改
IStudentDao dao = new StudentXxxImpl();
IStudentDao dao = new StudentYyyImpl();
//注意:接口是不会变化的,就是实现变了,操作数据库有很多技术实现:原生的JDBC,框架SpringJdbc,JPA,Mybatis
- 想要解决此类代码高耦合问题,那么就不能在使用new实现类的方式创建对象。需要使用到Spring的IOC和依赖注入DI
- 不new对象,那么意味着对象没有进行实例化,调用方法时就会报错。想要彻底解决我们可以将对象存储到一个容器中(此处就用到了IOC),想要使用对象时直接从容器中进行获取,将对象实例赋值给我们声明的对象,此处就会要到DI
**注意:**ConcurrentHashMap支持高效的并发访问,可以有效地提高系统的性能,ConcurrentHashMap 是一种线程安全的 HashMap,避免了高并发访问下线程安全问题,建议使用
3. IOC和DI概述
-
IOC控制反转
- IOC概述
IOC: Inversion Of Control控制反转,对象的创建权利由程序员主动创建转移到容器,由容器创建和管理对象,这种思想称为控制反转。这个容器称为IOC容器或Spring容器
控制:对象创建,属性赋值,对象生命周期管理【Bean的生命周期】 - 统称为管理对象 反转:把管理对象的权限转移给了容器实现,由容器完成对象的管理 正转:使用new构造方法创建对象,开发人员掌握了对象的全部过程
- 被IOC容器创建、管理的对象称为Bean对象
- IOC注解
想要在程序启动时将对象交由IOC帮我们创建并放入到容器中,那么我们需要告诉IOC哪些类需要交给它创建并管理,此时就需要在类上打上对应的注解
注解 | 说明 | 位置 |
---|---|---|
@Controller | @Component衍生注解,表示使用在控制器类上 | 标注在控制器类上 |
@Service | @Component衍生注解,表示使用在业务类上 | 标注在业务类上 |
@Repository | @Component衍生注解,表示使用在数据访问类上 | 标注在数据访问类上,使用MyBatis框架后不再使用 |
@Component | 声明bean的基础注解 | 不属于以上三类时,使用此注解 |
**注意:**这些注解要起作用,必须要被SpringBoot的启动类扫描到才有效
-
DI依赖注入
-
DI概述
对象创建出来就是为了使用它。使用IOC注解创建出来的对象,其实就是为了给属性赋值:
//使用IOC注解创建出来Dao层对象,是为了在Service业务层中调用Dao层对象方法时给成员变量Dao赋值 private IStudentDao studentDao; //使用IOC注解创建出来的Service层对象,是为了在Controller控制器中调用Service层对象方法给成员变量Sservice赋值 private IStudentService studentService;
DI:Dependency Injection依赖注入。Controller需要依赖Service对象,就可以使用依赖注入DI将Spring容器中的Servcice业务对象注入进去。同理Service需要依赖于Dao层对象,就可以使用依赖注入DI将Spring容器中的Dao层对象注入进去
-
DI注解
想要在IOC将对象创建完成后将Bean对象注入到我们声明的对象中时,需要使用到Spring提供的@Autowired注入注解打到声明对象上就可以实现从容器中自动获取对应实例进行注入
注解 说明 位置 @Autowired 依赖注入注解 标注在需要依赖注入的对象上
-
三层架构代码改造
使用IOC和DI直接改造三层架构代码
- 改造控制器Controller
- 改造业务代码Service
- 改造数据访问代码Dao
IOC详解
IOC控制反转其实就是将对象交给Spring管理。管理对象的创建、属性赋值DI、对象的作用域、对象的懒加载和生命周期
1. Spring容器
前面我们Spring是一个容器框架,其实底层就是一个ConcurrentHashMap,那么Map的结构是一个Key-Value,Value就是对象实例,那么Key是什么呢?
答:Key就是Bean的Name名称
2. Bean名称
Spring管理的对象称之为Bean。在IOC容器中,每一个Bean都有一个属于自己的名字,可以通过注解的value属性指定bean的名字。如果没有指定,默认为类名首字母小写
- 通过注解value属性指定名称,但建议用默认名字
3. 组件扫描
IOC注解和DI注解必须要被启动类扫描才能被识别,才能生效。也就是说使用IOC注解和DI注解的类所在包必须是跟启动类所在包平级或者在启动类所在包的子包下才行
如果不遵循以上规范,就会导致注解实现不了,程序运行不起来。当然也可以在启动类加上注解@ComponentScan手动指定扫描包的路径,麻烦得不要不要的
@ComponentScan:组件扫描注解,默认扫描启动类同级及以下所有包
4. Bean对象作用域
-
什么是Bean的作用域
Bean 的作用域是指Bean在Spring整个框架中的某种行为模式。比如singleton单例作用域,就表示 Bean 在整个 Spring中只有一份,它是全局共享的。当有人修改了这个值之后,那么另一个人读取到的就是被修改后的值
在Spring中,Bean的常见作用域有以下 5 种
作用域 说明 singleton 单例作用域,Bean对象默认作用域 prototype 多例作用域,每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例 request 请求作用域,只适用于SpringMVC框架,了解 session 会话作用域,只适用于SpringMVC框架,了解 application 全局作用域,只适用于SpringMVC框架,了解 -
改变Bean的作用域
@Scope注解可以作用在交给Spring管理的类上,使用value属性设置作用域。value属性:指定作用域
ConfigurableBeanFactory.SCOPE_PROTOTYPE:即"prototype" ConfigurableBeanFactory.SCOPE_SINGLETON:即"singleton",默认值 WebApplicationContext.SCOPE_REQUEST:即"request" WebApplicationContext.SCOPE_SESSION:即"session" WebApplicationContext.SCOPE_APPLICATION:即"application"
Bean对象懒加载
-
什么是懒加载
IOC容器中的Bean默认是在Spring启动时就进行加载并放入到容器中,有的时候我们并不需要对象在Spring启动时就进行加载,所以可以设置Bean对象为懒加载
迫切加载:Spring启动时就执行Bean对象初始化,默认方式 懒加载:在首次调用Bean对象时才进行Bean对象初始化
-
@Lazy注解
@Lazy注解可以作用在类上或者方法上,表示Bean对象开启懒加载
注意:使用@Lazy的前提是要操作的Bean要使用默认的单例模式,多例Bean无法开启懒加载,因为多例Bean本身就是使用时才创建
value属性:指定是否开启懒加载
- true:开启懒加载,默认值
- false:关闭懒加载
6. Bean的生命周期
什么是Bean的生命周期:生命周期指的是Spring框架中一个Bean对象从实例化到被垃圾回收器回收的这么一个过程,我们通常把这个过程称作,Bean的生命周期
简单理解,就是一个Bean对象从创建到销毁的这么一个过程,就好比一个人从出生到死亡的这么一个阶段一样
Spring中Bean的生命周期大致可以分为四个阶段,分别是:实例化、初始化、使用和销毁阶段。下面详细的介绍一下Bean的生命周期过程
- 实例化:Spring调用对象的构造方法创建Bean对象的过程
- 初始化:在Bean实例化完成之后,就会立即调用Bean的初始化方法,进行一些额外的处理操作,默认初始化方法习惯叫做【init()】
- 使用阶段:处于这个阶段的Bean对象,就可以真正的被使用了
- 销毁阶段:当某个Bean对象不再被使用,此时会首先调用销毁方法,默认销毁方法叫做【destory()】,用于释放一些系统资源,然后将Bean对象进行垃圾回收
案例代码:
@Component
//Spring管理的对象或Bean:默认单例(获取多次都是同一个对象),默认迫切加载(启动时就创建)
//@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) //多例配置
//@Lazy //配置懒加载
public class Dog {
public Dog() {
System.out.println("实例化...");
}
//也是将当前方法的返回值交给Spring管理,只能用在普通方法上
/*@Bean
public Dog getDog(){
return new Dog();
}*/
@PostConstruct
private void init() {
System.out.println("初始化...");
}
@PreDestroy //正常关闭服务器就会答应出来
private void destory() {
System.out.println("我还会再回来的...");
}
}
DI详解
1. DI概述
DI:Dependency Injection依赖注入,对象创建出来就是为了使用它。使用IOC注解创建出来的对象,其实就是为了给属性赋值
@Autowired注解翻译过来叫做自动装配,默认是按照类型去IOC容器中查找Bean对象进行自动装配。三层架构代码中使用@Autowired进行Bean对象的注入,这种方式称为属性注入,除了这种方式还有setter方法注入和构造器注入
2. 注入方式
- 属性注入
这种注入方式就是直接在类中声明的对象上使用**@Autowired**注解进行注入,例如像案例中一样。这种方式也是开发中最常使用的方式
-
Setter方法注入(了解)
Setter注入太过于臃肿,实际上很少使用,Spring官方更加推荐使用Setter方法注入
实现方式:在属性/字段对应的set方法上方添加@Autowired注解
**注意:**Setter方式注入的对象不能为多例或者懒加载,否则会找不到Bean对象,除非双方都是多例或者懒加载
-
构造器注入(了解)
如果类只有一个构造方法,那么@Autowired 注解可以省略。如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法
通过构造方法注入的方式,能够保证注入的组件不可变,并且能够确保需要的依赖不为空
3. 多个同类型Bean注入
@Autowired默认是按照类型进行注入,那么如果IOC容器中不止一个同类型的Bean对象是否还能注入成功呢?
创建两个IStudentService的子类,直接使用默认Bean名称。启动程序时报错说找到两个Bean实例
-
@Primary
@Primary:当存在多个相同类型的Bean注入时,在要使用的类上加上@Primary注解,来确定默认的实现
在StudentAServiceImpl类上加上@Primary
-
@Qualifier
@Qualifier注解:指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称
@Qualifier注解不能单独使用,必须配合@Autowired使用
-
@Resource
@Resource注解:是按照bean的名称进行注入。通过name属性指定要注入的bean的名称
**注意:**多个同类型Bean注入时,对象名如果为小写的实现类名,也可以注入成功。写的是哪个实现类的小写名,就会初始化那个实现类对象
4. @Resource与@Autowired区别
@Resource与@Autowired 两个注解都可以用来进行依赖注入
区别:
-
来源不同
@Autowired是Spring框架提供的注解,@Resource是JDK提供的注解
-
依赖查找顺序不同
-
@Autowired是先根据类型(byType)查找,如果存在多个(Bean)再根据名称(byName)进行查找。
-
@Resource查找顺序:先根据名称(byName)查找,如果(根据名称)查找不到,再根据类型(byType)进行查找
-
-
依赖注入支持不同
@Autowired
- 支持属性注入
- 支持setter注入
- 支持构造器注入
@Resource
- 支持属性注入
- 支持setter注入
**注意:**建议使用@Autowired,因为用的是spring框架,用spring的注解兼容性可能更好。(猜测)
综合案例
使用模块Blog、Pet等 + 三层架构 + lombok + IOC和DI注解
(页面过多),详情参见我的csdn
SpringBoot小结
1.Spring、SpringMvc、SpringBoot三个框架的区别
a.SpringBoot是一个简化配置,简化启动部署的全新框架。并不是用来替换Spring和SpringMVC的,只是用于简化Spring和SpringMvc的使用
b.SpringMvc框架是一个处理请求的mvc框架,底层对Servlet进行了封装
c.Spring框架是一个开源的容器框架,提供了IOC和AOP两大核心机制。IOC控制反转主要用于创建对象和对象的依赖关系,AOP面向切面编程主要用于抽取公共业务,解耦代码方便以后维护
2.目前在学习SpringBoot中常见的注解
a.实例化对象/管理对象的注解:
b.注入注解:
b1. 指定默认对象注解:
b2. 指定Bean名字注解:@Autowired+@Qualifier("orange")、@Resource(name="")
c.响应Json数据注解:@ResponseBody、@RestController
d.匹配请求注解:@RequestMapping、@XxxMapping、@PatchMapping
e.声明启动类的注解:
f.声明SpringBoot测试类的注解:SpringBootTest(classes=启动类.class)
3.何为起步依赖,作用是什么
4.简述SpringBoot的配置文件
5.SpringBoot中多环境配置如何实现
6.SpringBoot单元测试使用步骤
7.Apifox是什么,有什么用
8.常用的数据交换格式有哪些?xml和json区别
9.Controller(SpringMvc)接收参数有哪些方式
10.什么是ResultFul风格?有哪些请求方式分别描述哪些操作
11.什么是三层架构,为什么要使用三层架构
数据访问层Dao
业务逻辑层Service
表现层 - 控制器Controller
表现层 - 视图【客户端或页面】
12.三层架构规范
dao包
接口:IXxxDao
子包:impl 实现类XxxDaoImpl
service包
controller包
XxxController类
13.domain是什么,有什么用
域对象/实体对象, 在三层之间传递数据
14.指定Bean的对象作用域用什么注解,有哪些情况
@Scope - 单例singleton,多例prototype
15.什么是懒加载/延迟加载和迫切加载,如何配置Bean的懒加载
@Lazy
16.Spring中的Bean默认情况
单例,迫切加载
17.注入有哪些方式
属性注入,setter方法注入,有参构造
18.@Autowired与@Resource区别
扩展:什么是MVC?
MVC=Model+View+Controller=模型+视图+控制器,mvc是程序或软件设计的一种思想。就是设计或开发一个软件必须有三个部分
a.必须有View视图,用于能接触能看到的就是视图【页面或客户端】
b.视图中的功能必须要实现,就需要Model模型-实现视图的上的功能
c.模型和视图是不能直接交互的,所以需要一个桥梁,这个就是Controller。Controller控制器可以接收前端请求响应数据给前端,
同时它也是一个后端代码当前可以调用Model模型中的数据
```
e.声明启动类的注解:
f.声明SpringBoot测试类的注解:SpringBootTest(classes=启动类.class)
3.何为起步依赖,作用是什么
4.简述SpringBoot的配置文件
5.SpringBoot中多环境配置如何实现
6.SpringBoot单元测试使用步骤
7.Apifox是什么,有什么用
8.常用的数据交换格式有哪些?xml和json区别
9.Controller(SpringMvc)接收参数有哪些方式
10.什么是ResultFul风格?有哪些请求方式分别描述哪些操作
11.什么是三层架构,为什么要使用三层架构
数据访问层Dao
业务逻辑层Service
表现层 - 控制器Controller
表现层 - 视图【客户端或页面】
12.三层架构规范
dao包
接口:IXxxDao
子包:impl 实现类XxxDaoImpl
service包
controller包
XxxController类
13.domain是什么,有什么用
域对象/实体对象, 在三层之间传递数据
14.指定Bean的对象作用域用什么注解,有哪些情况
@Scope - 单例singleton,多例prototype
15.什么是懒加载/延迟加载和迫切加载,如何配置Bean的懒加载
@Lazy
16.Spring中的Bean默认情况
单例,迫切加载
17.注入有哪些方式
属性注入,setter方法注入,有参构造
18.@Autowired与@Resource区别
扩展:什么是MVC?
MVC=Model+View+Controller=模型+视图+控制器,mvc是程序或软件设计的一种思想。就是设计或开发一个软件必须有三个部分
a.必须有View视图,用于能接触能看到的就是视图【页面或客户端】
b.视图中的功能必须要实现,就需要Model模型-实现视图的上的功能
c.模型和视图是不能直接交互的,所以需要一个桥梁,这个就是Controller。Controller控制器可以接收前端请求响应数据给前端,
同时它也是一个后端代码当前可以调用Model模型中的数据