告别依赖混乱:Spring IoC 容器与 DI 依赖注入入门精讲
目录
什么是 IoC
IoC 介绍
传统开发思路
解决方法
IoC 优势
DI
IoC & DI 使用
IoC 详解
Bean 的存储
@Controller(控制器存储)
获取 bean 对象的其他方法
bean 命名
面试题之 ApplicationContext pk BeanFactory
@Service(服务存储)
@Respository(仓库存储)
@Component(组件存储)
@Configuration(配置存储)
为什么这么多类注解?
方法注解 @Bean
定义多个对象
扫描路径
DI 详解
属性注入(Field Injection)
构造方法注入(Constructor Injection)
Setter 注入(Setter Injection)
三种注入方式的优缺点
@Autowired 存在的问题
使用@Primary:
@Qualifier 注解:
@Resource 注解:
面试题之 @Autowird 与 @Resource 的区别
Spring,Spring Boot ,Spring MVC
完!
Spring 是包含了众多工具方法的 IoC 容器
List/Map -> 数据存储容器
Tomcat -> Web 容器
什么是 IoC
我们在前面已经使用过 IoC 了,在类上面添加 @RestController 和 @Controller 注解,就是把这个对象交给 Spring 管理,Spring 框架启动的时候,就会加载该类,把对西昂交给 Spring 管理,就是 IoC 思想。
IoC:Inversion of Control(控制反转),也就是说,Spring 是一个 “控制反转” 的容器。
控制反转什么呢??? 获得依赖对象的过程被反转了。也就是说,当需要某个对象的时候,传统开发中,需要我们自己 new 一个对象,现在不需要再进行创建了,把创建对象的任务交给了容器。我们在程序中,只需要进行依赖注入即可~~ 这个容器,就称为 IoC 容器,所以有时 Spring 也被称为 Spring 容器。
IoC 介绍
我们以一个具体案例来了解 IoC 思想。
当我们需要造一辆车的时候。
传统开发思路
先设计轮子,然后根据轮子的大小设计底盘,再根据底盘设计车身,再根据车身,对整个汽车进行设计。这样一层一层设计,就会出现一个依赖关系 ,即汽车依赖车身,车身依赖底盘,底盘依赖轮子。
上面的设计的可维护性非常低。当我们的需求发生一些变化的时候,随着对车的需求量越来越大,个性化需求也会越来越多,需要加工更多尺寸的轮胎,就需要对程序进行修改了。
即问题是:当最底层的代码发生改动之后,调用链上的所有代码,都需要进行修改。程序的耦合度极高,修改一处代码,会影响其他处的代码~~
解决方法
上面程序中,我们根据轮子的尺寸设计底盘,轮子尺寸修改,底盘就需要修改,同样的,后面的车身,汽车都需要修改。
我们可以先设计汽车的大概样子,然后根据汽车来设计车身,根据车身设计底盘,根据底盘设计轮子。
我们可以尝试,不在每个类中创建自己的下积累,如果自己创建下级类,当下级类发生改变操作的时候,我们自己也要跟着修改。
改用传递(注入)的方式。
IoC 优势
传统代码中创建对象的顺序是:Car->Framework->Bottom->Tire
改进解耦合之后的顺序是:Tire->Bottom->Framework->Car
改进之后,创建对象的控制权发生了反转,不再是使用方来进行对象创建并且控制依赖对象了,而是把依赖对象注入到当前对象中,依赖对象的控制权不再由当前类进行控制了。
这部分代码,就算 IoC 容器所作的工作。
IoC 容器可进行资源的几种管理,实现资源的可配置和易管理,当我们需要使用的时候,只需要从容器中取就可以了
IoC 容器也降低了使用资源双方的依赖程度,创建实例的时候并不需要了解细节,实现了解耦合~~
DI
DI:Dependency Injection (依赖注入)
容器在运行期间,动态的为应用程序提供运行时所依赖的资源。依赖注入是从应用程序的角度进行描述,指通过引入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦合
IoC & DI 使用
Spring 是一个 IoC 容器,管理的主要是对象,这些对象,我们称之为 Bean。Spring 来负责这些对象的创建和销毁,我们程序只需要告诉 Spring,那些需要存,以及如何从 Spring 中取出对象。
实现:
1. 把 BookDao 交给 Spring 管理,在 BookDao 类前添加 @Component 注解
2. 把 BookService 交给 Spring 管理,由 Spring 来管理对象
3. 删除 BookService 中创建 BookDao 的代码,从 Spring 中获取对象,添加 @Autowired 注解
4. 删除 BookController 中创建 BookService 的代码,从 Spring 中获取对象,添加 @Autowried 注解
IoC 详解
系统的学习 Spring IoC 和 DI 操作。前面提到的 IoC 控制反转,就是将对象的控制权交给 Spring 的 IoC 容器,由它对对象进行创建和管理。
也就是 bean 的存储。
Bean 的存储
之前的案例中,我们要把对象交给 IoC 容器管理,需要在类上添加一个注解:@Component
Spring 框架中,提供了更丰富的注解。
@Controller(控制器存储)
从 Spring 容器中获取对象
补充:因为我们的对象通过 @Controller 注解都交给 Spring 管理了,所以获取对象要通过 Spring 获取,那么就需要先得到 Spring 的上下文,即 ApplicationContext 翻译就是 Spring 上下文~
此时,如果我们把 @Controller 删掉,运行结果如下:
表明在 Spring 中找不到指定类型的 Bean
获取 bean 对象的其他方法
通常使用 1 2 4 三种方式获取 bean,且获取到的 bean 都是一致的
其中 1 2 都涉及到根据名称来获取对象,再来研究一下 bean 命名约定~
bean 命名
官方文档中写道:程序开发人员并不需要为 bean 指定名称,如果没有显示的提供名称,Spring 容器会为该 bean 生成唯一的名称。
命名约定就使用 Java 标准约定作为实例字段名。即,bean 名称以小写字母开头,然后使用驼峰式大小写~
也有特殊情况,即,当由多个字符,且第一个和第二个字符都是大写的时候,将会保留原始的大小写~
我们根据规则,再来进行 bean 的获取:
三条 sout 语句的结果一致
地址一样,说明对象是同一个~
面试题之 ApplicationContext pk BeanFactory
继承关系和功能方面:Spring 容器的两个顶级的接口:BeanFactory 和 ApplicationContext。其中,BeanFactory 提供了基础的访问容器的能力,ApplicationContext 属于 BeanFactory 的子类,出了继承了 BeanFactory 的功能之外,还有了其他方面的特性~
性能方面:BeanFactory 是懒加载,用到那个加载那个。ApplicationContext 是一次性加载并初始化所有的 Bean 对象~
@Service(服务存储)
去掉 @service 注解后,仍然报错:
@Respository(仓库存储)
@Component(组件存储)
@Configuration(配置存储)
为什么这么多类注解?
@Controller:控制层,接收请求,对请求进行处理,并进行响应。
@Service:业务逻辑层,处理具体的业务逻辑。
@Respository:数据访问层,负责数据访问操作
@Configuration:配置层,处理项目中的一些配置信息
当我们对五大类注解的源码进行观察后
除 @component 之外,剩下四个注解的源码中均有 @Component 注解。说明他们本身都是属于 @Component 的子类,@Component 是一个元注解,可以对其他注解进行注解~~
剩下四个注解,可以被称为 @Component 的衍生注解~~
其实从功能上来说,五大注解大差不多可以混用,但是在逻辑上是不可以的~~
方法注解 @Bean
类注解是添加到某个类上的,但有两个问题:
1. 使用外部包中的类,没办法添加类注解
2. 一个类,需要多个对象,多个数据源~
这种情况,我们需要使用 @Bean 注解
结果如下:
方法注解是要配合类注解进行使用的。
在 Spring 框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中
定义多个对象
即,多数据源的情况,类是同一个,但配置不同,指向不同的数据源
此时,我们根据类型获取对象:
运行结果:
期望是获取到唯一一个匹配,结果发现了两个 user1 和 user2
从报错信息来分析,@Bean 注解的 bean,其名称就是它的方法名。
根据名称来获取 bean 对象:
此时可以成功获取到~
也可以对 bean 的名称进行重命名
扫描路径
Q:使用前面我们提到的四个注解生命的 bean,一定会生效吗?
A:不一定(bean 要想生效,还需要被 Spring 扫描到)
其他代码不懂,仅仅修改目录结构:
又会发现,找不到名称为 u1 的 bean。
使用五大注解声明的 bean,要想生效,还需要配置扫描路径,让 Spring 扫描到这些注解~
使用 @ComponentScan 来配置扫描路径
这种做法仅作了解~
那为什么前面我们并没有配置这个直接,也可以正常使用呢?
@SpringBookApplication 这个启动类声明注解中,已经包含 @ComponentScan 注解了~
会默认扫描 SpringBoot 启动类所在的包及其子包~
一般我们将启动类直接放在我们要被扫描的包的路径下即可~
DI 详解
依赖注入是一个过程,值 IoC 容器在创建 Bean 的时候,去提供运行时所依赖的资源(对象)
上面案例中,我们是使用了 @Autowired 注解,完成了依赖注入的操作。
简单来说,就是把对象取出来,放入某个类的属性中。
依赖注入,Spring 也提供了三种方式
属性注入(Field Injection)
属性注入是使用 @Autowired 实现的,将 Service 类注入到 Controller 类中
运行结果:
去掉 @Autowired 的时候,结果如下:空指针异常,找不到 userService
构造方法注入(Constructor Injection)
注:
1. 如果只有一个构造函数的情况下,@Autowired 可以省略
2. 如果有多个构造函数,需要指定默认的构造函数(@Autowired 指定)如果未指定,则默认使用无参的构造函数
3. 构造函数规范:如果添加构造函数,把无参构造函数显示的进行添加~
Setter 注入(Setter Injection)
Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set 方法时候,需要加上 @Autowired 注解
三种注入方式的优缺点
@Autowired 存在的问题
当同一个类中,存在多个 bean 的时候,使用 @Autowired 会存在问题
运行结果:
报错的原因是:非唯一的 Bean 对象
解决方案:
@Primary
@Qualifier
@Resource
使用@Primary:
当存在多个相同类型的 Bean 注入时候,加上 @Primary 注解,来确认默认的实现~
@Qualifier 注解:
指定当前要注入的 bean 对象。在 @Qualifier 的 value 属性中,指定注入的 bean 的名称
注:Qualifier 注解不能单独使用,需要搭配 @Autowired 使用
@Resource 注解:
是按照 bean 的名称进行注入。通过 name 属性指定要注入的 bean 的名称
面试题之 @Autowird 与 @Resource 的区别
1. @Autowired 是 Spring 框架提供的注解,而 @Resource 是 jdk 提供的注解
2 @Autowired 默认是按照类型注入的,而 @Resource 是按照名称注入,相比 @Autowired 来说,@Resource 支持更多的参数设置~