1.IoC&DI入门
在前面,我们知道了Spring是一个开源框架,让我们开发变得更简单,它支持广泛的应用场景.
更具体来说,Spring是包含了众多工具方法的IoC容器.
什么是容器?
简单来说,在编程中,List/Map这种数据结构就是数据存储容器,Tomcat就是web容器.
1.1 什么是IoC
IoC是Spring的核心思想,其实我们在之前的学习中已经用到了IoC了.
在使用类上使用@RestController,@Controller注解的时候,就是把这个对象交给了Spring进行管理.Spring框架在启动的时候就会加载这个类.
把对象交给Spring管理,就是IoC思想.
IoC: Inversion of Control.字面意思翻译:控制反转.
也就是说Spring是一个"控制反转"的容器.
1.2 IoC介绍
现在有一个需求,需要创建一个车.
车类:
java
public class Car {
private Framework framework;
public Car(){
this.framework = new Framework();
System.out.println("cat init...");
}
public static void main(String[] args) {
Car car = new Car();
car.run();
}
public void run(){
System.out.println("car run...");
}
}
车身类:
java
public class Framework {
private Bottom bottom;
public Framework() {
bottom = new Bottom();
System.out.println("Framework init...");
}
}
车底座类:
java
public class Bottom {
private Tire tire;
public Bottom() {
this.tire = new Tire();
System.out.println("Bottom init...");
}
}
轮胎类:
java
public class Tire {
private int size = 17;
public Tire(){
System.out.println("Tire:"+ size);
}
}
运行程序:

这样的设计看起来好像没什么问题,但是可维护性缺很低.
如果需求变更,比如需要不同轮胎大小的车,此时我们就需要指定size属性的值,而不是在创建Tire的时候就固定size的值.

但是此时会报错,我们需要将其他所有的类全部都指定size,才能把参数传过来.




改完所有的代码之后,才能正常运行,
这时候,我们就发现,以上程序的问题:当底层代码被改动之后,整个调用链往上的所有代码都需要改动,可以发现这个程序的耦合程度非常高(修改一处代码,影响其他代码的修改)
那么如何解决这个问题呢?
这个问题出现的原因,就是在创建类的时候,会同时创建下级类,比如我在创建Car的时候,就需要创建Framework类,创建Framework类,又需要创建Bottom类...
这样整个创建过程全部都连一块了,牵一发而动全身.
我们只需要不在当前类中创建下级类,就可以完成程序的解耦.
车类:
java
public class Car {
private Framework framework;
public Car(Framework framework){
System.out.println("cat init...");
}
public static void main(String[] args) {
Tire tire = new Tire(20);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
public void run(){
System.out.println("car run...");
}
}
车身类:
java
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
System.out.println("Framework init...");
}
}
底座类:
java
public class Bottom {
private Tire tire;
public Bottom(Tire tire) {
System.out.println("Bottom init...");
}
}
轮胎类:
java
public class Tire {
private int size;
public Tire(int size){
System.out.println("Tire:"+ size);
}
}
上面这套代码,当我们需要改动尺寸的时候,只需要改动这个代码

剩下的就不需要改了,这是因为类的创建被我们放在了main函数中,无论底层怎么变化,调用链是不需要改变的,这样就完成了代码的解耦.
这种思想就是IoC思想.将程序中的对象的创建权和生命周期的管理权交给框架(这里是Spring)管理.
控制反转,也就可以理解为控制权从程序员手里反转到了Spring框架手里.
1.3 IoC的优势:
在传统的代码中,创建对象的顺序是:Car -> Framework -> Bottom -> Tire
解耦之后的代码创建顺序是: TIre -> Bottom -> Framework -> Car
在之前的代码中,Car控制并创建了Framework,Framework控制并创建了Bottom,以此类推......
这种代码,Car,Framework,Bottom,Tire 的生命周期是绑死在一起的,也就是说当Car对象没用的时候,被GC回收之后,依赖Car创建的Framework,Bottom,Tire会一起被回收.
同时这套代码无法复用,比如我只想换个Bottom,但是在new的时候,全部绑死在一起了,无法单独更换
当我们使用IoC进行更改代码之后,控制权发生反转了,所有的对象统一由Spring进行控制管理,这个时候,我们想换某一个部件就变得非常简单了.
更改之后的代码,不再是使用方创建并控制对象了,而是把依赖对象注入到当前对象中,依赖对象的控制权并不是由当前对象控制了.
这样即使依赖对象发生改变,当前的类也不会有什么变化,这就是典型的控制反转,也就是IoC的实现思想.
那么我们知道了什么是IoC思想,那么什么是IoC容器呢?
简单来说,IoC容器就是用来管理和创建对象的一个容器 .在当前代码,我们把创建和管理对象的权限交给了Spring框架,所以这里Spring框架也就是一种IoC容器.
IoC容器做的就是这下面的工作.

IoC容器具备以下优点:
资源不由使用资源的双方管理,而由不使用资源的第三方管理 ,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理 。第二,降低了使用资源双方的依赖程度.
1.资源集中管理: IoC 容器会帮我们管理一些资源 (对象等), 我们需要使用时,只需要从 IoC 容器中去取就可以了
2.我们在创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,也就是耦合度.
1.3 DI介绍
学习了IoC,让我们了解一下什么是DI
DI:Dependency Injection,中文翻译,依赖注入.
容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称为依赖注入.
在上面的代码,是通过构造函数的方式,把依赖对象注入到要使用的对象中的.

IoC属于一种思想,最终也需要一种实行方案,而DI就属于具体的实现.所以说DI是IoC的一种实现.
2.IoC&DI使用
对IoC和DI有了初步的了解,我们先学习SpringIoC和DI的具体代码实现.
既然Spring作为IoC容器,那么他就具备两个容器的基础功能:存和取.
Spring容器管理的主要是对象,这些对象,我们称为"Bean".
我们把这些对象交由Spring进行管理,由Spring负责对象的创建和销毁.我们的程序只需要告诉Spring那些需要存,以及如何从Spring中取出对象.
目标:把BookDao,BookService交给Spring管理,完成Controller层,Service层,Dao层的解耦.
步骤:1.Service层和Dao层的实现类,交给Spring管理,用@Component注解
2.在Controller层和Service层注入运行时候的依赖,用@Autowired注解
实现:
1.把BookDao和BookService交给Spring进行管理.由Spring管理对象.


2.删除创建BookDao的代码,从Spring中获取对象
更改前:

更改后:

3.删除创建BookService的代码,通过@Autowired从Spring中获取.
更改前:

更改后:

重新运行程序,发现程序功能正常.
3.IoC详解
通过上面的案例,我们已经知道了SpringIoC和DI的基本操作.接下来我们就来学习SpringIoC和DI的操作.
前面我们提到IoC控制反转.就是将对象的控制权交给SpringIoC容器.由IoC容器创建及管理对象,也就是Bean的存储.
在上面的案例中,我们要将一个对象交给IoC容器管理,需要在这个类上加上@Component注解.
而Spring框架为了更好的服务web应用程序,提供了更丰富的注解.
类注解:@Controller,@Service,@Component,@Repository,@Configuration
方法注解:@Bean
3.1 @Controller(控制器存储)
使用@Controller存储bean的代码:

我们如何知道这个对象已经在Spring中了呢?
接下来学习如何从Spring中获取对象,

运行代码:
ApplicationContext翻译过来就是Spring上下文
因为对象交给Spring管理了,所以说要想获取对象,得先获取Spring上下文(指的是Spring当前的运行环境).
如果我们把@Controller注解删掉,再次运行看看结果.发现系统给我们报错了.
这个错误信息告诉我们没有找到类型是bit.demo.ioc.UserController的bean

获取Bean的其他方式:
1.通过对象名获取,对象名就是类名把开头第一个字母改为小写,如果前两个字母都是大写就不变
比如UserController -> userController ,AManager -> AManager
2.通过类名获取
3.通过类名+对象名获取

打印这三个对象,发现是同一个对象.


3.2 @Service(服务存储)/ @Repository(仓库存储)/ @Component(组件存储)/ @Configuration(配置存储)
把之前的@Controller注解换成这四个注解,可以看到代码的效果依旧可以正常运行.
这四个注解的作用和@Controller作用是一样的,唯一的区别就是用来标识当前类的作用


这个也是和咱们前面讲的应用分层是呼应的。让程序员看到类注解之后,就能直接了解当前类的用途.
- @Controller: 控制层,接收请求,对请求进行处理,并进行响应.
- @Service: 业务逻辑层,处理具体的业务逻辑.
- @Repository: 数据访问层,也称为持久层。负责数据访问操作
- @Configuration: 配置层。处理项目中的一些配置信息.
3.3 @Bean
类注解是添加到类上的,但是存在两个问题:
1.使用外部包的类,就没办法添加类注解.
2.一个类,需要多个对象,比如多个数据源
这种时候,就需要使用@Bean注解.
方法注解需要搭配类注解使用,单纯的@Bean是没办法用的.

创建一个Student类,添加一个sutdent方法,在方法上面添加@Bean注解,但是不在类上面添加@Component注解.
尝试运行.
报错显示找不到这个类

在类上添加@component注解,再次运行.就可以拿到值了


3.4 定义多个对象
对于同一个类,如何定义多个对象呢?
比如这里有两个学生,一个叫张三,一个叫李四.

此时运行程序,发现报错说明期望只有一个结果匹配,结果发现两个.

从报错信息中我们可以知道@Bean注解的bean的名称就是他的方法名
下面我们根据名称获取对象.就可以成功拿到对象


3.5 重命名Bean
通过在@Bean注解中设置name属性就可以给Bean对象重命名,这样也可以拿到bean,这个重命名的名称可以有多个,用大括号包起来即可.



3.6 扫描路径
在前面学习的四个注解声明的bean一定会生效吗?答案是不一定,bean要生效,必须要被spring扫描.
下面我们通过更改项目工程的目录,来测试bean是否生效,

把启动路径更改位置.改到car2包里面

系统就会报错,显示没有找到这个bean

为什么会报错呢?这是因为要使用五大注解,必须要配置扫描路径,让spring扫描到这些注解.
通过@ComponentScan这个注解配置扫描路径.默认的路径是SpringBoot启动类所在的包以及子包
在启动类上添加扫描路径,就可以正确扫描到注解.


4.DI详解
刚刚我们讲解了控制反转IoC的细节,接下来我们学习依赖注入DI的细节.
在上面的案例中,我们通过@Autowired这个注解来完成依赖注入.
Spring给我们提供了三种依赖注入的方式,接下来一个一个学习.
4.1 属性注入
属性注入是使用@Autowired实现的,将Service类注入到Controller类中



运行结果,就可以实现依赖注入

4.2 构造方法注入
和属性注入的区别,构造方法注入就是将@Autowired注解加到构造方法上面,如果只有一个构造方法,@Autowired注解可以省略,如果有多个,需要用@Autowired来指明使用哪个构造方法.

4.3 Setter注入
Setter注入和属性的Setter方法实现类似,只不过在设置set方法的时候,要加上@Autowired注解.

4.4 三种注入优缺点分析
属性注入
优点:简洁,使用方便;
缺点:
1.只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)
2.不能注入一个 Final 修饰的属性
构造函数注入 (Spring 4.X 推荐)
优点:
1.可以注入 final 修饰的属性
2.注入的对象不会被修改
依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法.
通用性好,构造方法是 JDK 支持的,所以更换任何框架,他都是适用的
缺点:注入多个对象时,代码会比较繁琐
Setter 注入 (Spring 3.X 推荐)
优点 :方便在类实例之后,重新对该对象进行配置或者注入
缺点:
1.不能注入一个 Final 修饰的属性
2.注入对象可能会被改变,因为 setter 方法可能会被多次调用,就有被修改的风险.
4.5 @Autowired存在的问题
当我们在UserController中注入两个属性之后,启动程序会失败,显示发现两个bean


当有多个bean的时候,这时候可以在bean上面加上@Primary注解,来确定默认实现

我们还可以使用@Qualifier注解,指定要注入的bean的名称

或者使用@Resource指定bean的名称

常见面试题:
@Autowird 与 @Resource 的区别:
@Autowired 是 spring 框架提供的注解,而 @Resource 是 JDK 提供的注解
@Autowired 默认是按照类型注入,而 @Resource 是按照名称注入。相比于 @Autowired 来说,
@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean.
5.练习
将我们上次的图书管理系统中的注解改为今天学习的注解
将Dao层的注解改为@Repository
将Service层的注解改为@Service