Spring 的IoC/DI

本文深入探讨了IoC(控制反转)和DI(依赖注入)的概念,解释了它们如何降低对象之间的耦合度,提高代码的可测试性和复用性。详细介绍了IoC容器的工作原理,包括Bean的定义、注册和依赖注入过程,以及Bean的不同作用域和生命周期。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1  什么是IoC

IoC(Inversion of Control),即控制反转,是一种思想,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

传统程序设计:通过代码(new等)主动创建对象,创建对象的依赖对象,并主动关联依赖对象

IoC:由专门一个容器来创建这些对象,自动创建对象的依赖对象并注入,且管理这些对象的生命周期

2 IoC的作用

传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试

IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松耦合,这样也方便测试,利于功能复用

从思想上,发生了“主从换位”的变化。应用程序原本要获取什么资源都是主动去获取,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源。即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找

3 Ioc和DI

DI(Dependency Injection),即依赖注入,所谓依赖注入,就是比如某个对象需要另一个对象,则容器在运行的时候,创建这个依赖,并将这个依赖注入到该对象中。组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入提高了组件的复用性。我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

Ioc和DI的关系:

它们是同一个概念的不同角度描述,依赖注入比控制反转更加易于理解。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象,这一点是通过DI来实现的。

4 IoC的优缺点

优点

  • 对象与对象之间是松耦合,复用性好
  • 可维护性比较好

缺点

  • 由于引入了第三方IOC容器,生成对象的步骤变得有些复杂
  • 对象生成因为是使用反射编程,在效率上有些损耗

5 IoC容器

springIoC的实现使用了反射工厂模式

Spring中定义了两个接口用以表示容器:

  • BeanFactory
  • ApplicationContext

 

BeanFactory 可以称之为 “低级容器”,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put)获取(get)这两个功能。最主要的方法就是getBean(String beanName)

ApplicationContext 可以称之为 “高级容器”。ApplicationContext 建立在BeanFactory的基础上因为除了能够提供IoC容器的基本功能外,他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。

  1. 支持信息源,可以实现国际化。(实现MessageSource接口)
  2. 访问资源。(实现ResourcePatternResolver接口,这个后面要讲)
  3. 支持应用事件。(实现ApplicationEventPublisher接口)

所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。

几乎所有的应用场合都可以直接使用ApplicationContext而非底层的BeanFactory

该接口定义了一个 refresh 方法,用于刷新整个容器,即重新加载/刷新所有的 bean。

 

6 IoC容器的初始化过程

IoC容器的初始化包括三个基本的过程:

  1. Resource定位(Bean的定义文件定位)
  2. 将Resource定位好的资源载入到BeanDefinition
  3. 将BeanDefiniton注册到容器中
  • 第一步 Resource定位

Resource是什么:是Sping中用于封装I/O操作的接口,创建spring容器时,通常要访问XML配置文件,除此之外还可以通过访问文件类型、二进制流等方式访问资源,还有当需要网络上的资源时可以通过访问URL,Spring把这些文件统称为Resource

ApplicationContext的所有实现类都实现RecourceLoader接口,因此可以直接调用getResource(参数)获取Resoure对象

  • 第二步 通过返回的resource对象,进行BeanDefinition的载入

BeanDefinition:SpringIOC容器管理了我们定义的各种Bean对象及其相互的关系,Bean对象在Spring实现中是以BeanDefinition来描述的,BeanDefinition相当于一个数据结构,这个数据结构的生成过程是根据定位的resource资源对象中的bean而来的,IoC容器对bean的管理和依赖注入的实现都是通过操作BeanDefinition来进行的。

在Spring中配置文件主要格式是XML,IoC 容器会使用到AbstractXmlApplicationContext类的loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用于获取BeanDefinition

此方法在具体执行过程中使用一个BeanDefinitionReader型实例对象,来进行使用BeanDefinitionReader的loadBeanDefinitions方法载入Resource。对resource进行处理,并生成BeanDefinition对象。

  • 第三步,将BeanDefiniton注册到容器中

将BeanDefinition注册到DefaultListableBeanFactory.beanDefinitionMap中。key是类名,value是实例对象。

7 IOC容器的依赖注入

当Spring IoC容器完成了Bean定义资源的定位、载入和解析注册以后,IoC容器中已经管理类Bean定义的相关数据,但是此时IoC容器还没有对所管理的Bean进行依赖注入,依赖注入发生在:

用户第一次通过getBean方法向IoC容索要Bean时,IoC容器触发依赖注入

对于单例的bean,容器会在初始化的时候就进行依赖注入,但是如果在该Bean定义时配置了lazy-init属性,即延迟加载,则该单例bean的依赖注入发生在用户第一次调用getBean方法获取该bean时

依赖注入有三种注入方式:构造器、接口、set注入。我们常用的是set注入

8 Bean的作用域

singleton

spring中的bean的默认作用域,当一个bean的作用域为singleton, 那么Spring IoC容器中只会存在一个共享的bean实例,即单例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。

prototype

Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean() 方法)时都会创建一个新的bean实例。根据经验,对所有有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用 singleton作用域

request

一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用 域仅在基于web的Spring ApplicationContext情形下有效。

session

一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

global session

一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于 web的Spring ApplicationContext情形下有效。

9 Bean的生命周期

Bean实例生命周期的执行过程如下:

  1. Spring对bean进行实例化,默认bean是单例;
  2. Spring对bean进行依赖注入
  3. 如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;
  4. 如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;
  5. 如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;
  6. 如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用;
  7. 如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet接口方法,类似的如果bean使用了init-method属性声明了初始化方法,则再调用该方法;
  8. 如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用;
  9. 此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁;
  10. 若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。如果bean使用了destroy-method属性声明了销毁方法,则再调用该方法;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值