Spring 两大核心思想(一):IoC
- [1. 什么是 IoC](#1. 什么是 IoC)
- [2. Bean 的存储和获取](#2. Bean 的存储和获取)
-
- [2.1 类注解](#2.1 类注解)
- [2.2 方法注解 @Bean](#2.2 方法注解 @Bean)
- [2.3 Bean 的命名规则](#2.3 Bean 的命名规则)
- [2.4 获取 Bean](#2.4 获取 Bean)
- [3. DI 依赖注入](#3. DI 依赖注入)
-
- [3.1 三种注入方法](#3.1 三种注入方法)
- [3.2 同一类型有多个 Bean 时怎么注入](#3.2 同一类型有多个 Bean 时怎么注入)
- [4. IoC 的好处](#4. IoC 的好处)
IoC 是 Spring 的两大核心思想之一,而 DI 是 IoC 的具体落地实现,二者是 Spring 所有功能的基石,也是 Java 后端面试的高频核心考点。本文从概念本质出发,详细梳理 IoC 相关知识点,讲解详细,欢迎收藏!
1. 什么是 IoC
IoC:Inversion of Control(控制反转)
IoC 是一种思想,将对象的控制权交给 IoC 容器,由 IoC 容器来创建对象,而不是自己去 new 一个对象。使用时,将依赖注入(DI)进来即可,这里的依赖其实就是对象。
简单来说,对象的控制权从程序员代码交给了 IoC 容器,就是控制权实现了反转。
Spring 框架实现了 IoC 思想,所以,Spring 是一个包含了众多方法的 IoC 容器。
2. Bean 的存储和获取
被 Spring IoC 管理的对象,叫作 Bean。要想让对象被 Spring 管理,我们要把它们存储到 IoC 容器中。可以通过以下注解实现。
2.1 类注解
我们可以通过五大注解 实现对象的存储,这五大注解是类注解,加在类上:
- @Controller:控制层
- @Service:业务逻辑层
- @Repository:数据访问层
- @Component:其他
- @Configuration :配置层
这五大注解与程序的应用分层是相呼应的,让程序员看到注解后就能直接了解当前类的用途。根据类所在的层,在类上加上对应的注解就可以实现 Bean 的存储。
@Controller、@Service、@Repository、@Configuration 中都实现了 @Component,它们可以理解为是 @Component 的"子类",所以在一些分层比较模糊的场景,我们不用纠结到底使用哪个注解,它们也可以通用。
2.2 方法注解 @Bean
还有一个方法注解 :@Bean。
@Bean 可以解决两种情况:
- 当想要使用的类在外部的包(第三方依赖)中时,我们没法对该类添加注注解
- 当一个类中需要创建多个对象时
此时,在对应方法上@Bean 即可。
【注意】@Bean 必须搭配五大注解一起使用。
Bean 的命名规则:被 @Bean 修饰的方法名,就是当前 Bean 的默认名称。
2.3 Bean 的命名规则
开发人员不需要为 Bean 指定名称,Spring 容器会自动为 Bean 生成唯一的名称:
- 如果类的名称是大驼峰形式,Bean 的名称是将类名第一个字母小写,即变为小驼峰。(UserCroller --> userController)
- 如果类的名称前两个字母都是大写,类名即为 Bean 的名称。(UController --> UController)
- 如果是 @Bean 存储的对象,@Bean 修饰的方法名即为 Bean 的名称,也可通过 @Bean("name") 自定义名称。
2.4 获取 Bean
有三种方式:根据名称获取、根据类型获取、根据名称 + 类型获取
首先从需要调用 ApplicationContext 提供的 getBean 方法,从 Spring 上下文中获取对象。假设我们要获取的对象类型为 UserController,代码如下:
java
@SpringBootApplication
public class SpringApplication {
public static void main(String[] args) {
// 获取 Spring 上下文
ApplicationContext context =
SpringApplication
.run(SpringIocDemoApplication.class, args);
// 从 Spring 上下文中获取对象
// 1. 根据名称获取
UserController userController1 =
(UserController) context.getBean("userController");
// 2. 根据类型获取
UserController userController2 =
(UserController) context.getBean(UserController.class);
// 3. 根据名称 + 类型处理
UserController userController3 =
(UserController) context.getBean("userController", UserController.class);
}
}
在上面的代码中,我们用不同方式三次获取了 Bean,打印他们的地址可以发现,它们是同一个对象。也就是说,Spring 创建的对象是单例的。因为 Spring 默认使用单例池,只创建一个对象,全局复用。
此外,ApplicationContext 获取 Bean 对象的功能,是其父类 BeanFactory 提供的功能。ApplicationContext 和 BeanFactory 的主要区别有:
- 继承关系方面,ApplicationContext 继承自 BeanFactory,BeanFactory 提供了基础的访问容器的功能,而 ApplicationContext 除了继承了 BeanFactory 的所有功能之外,还添加了对国际化支持、资源访问支持以及事件传播等方面的支持。
- 性能方面,ApplicationContext 是一次性加载并初始化所有 Bean 对象,BeanFactory 是用到了才去加载,因此 BeanFactory 更加轻量。
3. DI 依赖注入
DI (Dependency Injection) 依赖注入:是 IoC 的具体实现方式。
DI 是一个过程,是指 IoC 容器在创建 Bean 时,动态地去提供运行时所依赖的资源,这里的资源就是对象。简单来说,就是容器自动把依赖的 Bean 取出,放到某个类的属性中的过程。
3.1 三种注入方法
关于依赖注入,Spring 给我们提供了 3 种方法:
- 属性注入:将 @Autowired 方法加在属性上
代码示例:将 userService 注入到 UserController 中
java
@Controller
class UserController {
@Autowired
private UserService userService;
...
}
优点 :代码简单,使用方便。
缺点:不能注入 final 修饰的对象;只能用于 IoC 容器,非 IoC 容器不可用。
- 构造方法注入:在类的构造方法上加上@Autowired
代码示例:将 userService 注入到 UserController 中
java
@Controller
class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
...
}
如果构造方法唯一,可以不加 @Autowired,会自动注入;如果有多个构造方法,必须在一个上加上 @Autowired,指定需要使用哪个构造方法。
【注意】使用构造函数必须把无参构造函数显示地添加(不管用不用),否则报错。
优点 :可以修饰 final 修饰的对象;注入对象不会被修改;依赖对象在使用前一定会被初始化,避免了空指针异常(因为是在构造方法中注入的,而构造方法在类加载阶段就执行了);通用性好,此方法是 JDK 支持的,其他框架也适用。
缺点:注入多个对象时,代码繁琐。
- Setter 方法注入:在需要注入属性的 Setter 方法上加上 @Autowired
代码示例:将 userService 注入到 UserController 中
java
@Controller
class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
...
}
优点 :方便在类实例化后,重新对该对象进行配置或修改。
缺点:不能注入 final 修饰的属性;注入的对象可能会被改变,因为 setter 方法可能会被多次调用。
3.2 同一类型有多个 Bean 时怎么注入
当我们使用 @Bean 注解,一个类中将多个 Bean 交给 IoC 容器,这时,同一类型的 Bean 就会有多个。这种情况下 @Autowired 就会报错,原因是非唯一的 Bean。Spring 提供了以下几种解决方案:
- @Primary:设置默认优先的 Bean
在其中一个对象上加上 @Primary,表示是默认要注入的 Bean。
java
@Component
public UserInfo {
@Primary
@Bean("user1") //对对象进行重命名
public User user1() {
...
}
@Bean("user2")
public User user2() {
...
}
}
- @Qualifier:指定 Bean 的名称
在 @Qualifier 的属性 value 中,指定要注入的 Bean 的名称。
【注】@Qualifier 不能单独使用,必须搭配 @Autowired 一起使用。
java
public class UserController {
@Qualifier(value="userService1")
@Autowired
private UserService userService;
...
}
- @Resourse:默认按名称注入
默认根据名称匹配 Bean,在 @Resourse 的 name 属性中指定要注入的 Bean 的名称。
java
public class UserController {
@Resourse(name="userService2")
private UserService userService;
...
}
@Autowired 和 @Resourse 的区别:
- @Autowired 是 Spring 提供的注解,而 @Resourse 是 JDK 提供的注解。
- @Autowired 支持按类型注入,而 @Resourse 默认按名称注入。
- 相比于 @Autowired,@Resourse 支持更多属性设置,如 name 属性,根据名称获取 Bean。
4. IoC 的好处
最后总结一下 IoC 的好处:
- 资源集中管理:Ioc 容器会帮我们管理一些资源(对象等),我们使用时,只需从 Ioc 容器中去取就可以。
- 我们在创建实例的时候不再需要了解其中的细节,无需手动 new 对象,降低了使用资源双方的依赖程度(耦合度)。
- 支持单例复用:Spring 默认单例,减少了对象的创建开销,提升性能。