在日常开发中,接触得比较多的算是Spring生态了,Spring Ioc是Spring Framework重要的组成部分,下面整理了一些Spring Ioc的知识点。

1. 什么是IoC

IoC(Inversion of Control),翻译过来就是控制反转,它是一种设计思想,这个设计思想说明了 “由谁控制,控制了什么,为何反转,反转了什么”。

  • 控制

Java通过new来创建对象,而IoC有专门的容器控制对象创建,即Ioc控制了对象的创建(实际还包含更多的外部资源,如配置等)

  • 反转

使用IoC容器管理对象时,对象持有的依赖对象是由IoC注入的,即对象只是被动的接受了依赖对象,也即依赖对象的获取反转了。

如:需要对 UserService 这个类进行单元测试,其中 UserService 还依赖 UserAddressService:

public class UserServiceTests {
public void test() {
// ...
}
} public class UserService {
public UserAddressService userAddressService;
} public class UserAddressService {
}

那么在没有IoC的情况下,UserServiceTests.test() 测试流程为:

  1. 创建UserService对象
  2. 创建UserAddressService对象
  3. 将UserAddressService对象注入到UserService中

而在使用IoC的情况下,UserServiceTests.test() 测试流程则为:

  1. UserServiceTests获取UserService对象
  2. IoC控制:
    1. 创建UserService对象
    2. 创建UserAddressService对象
  3. IoC反转:
    1. 将UserAddressService对象注入到UserService中

可以用“别找我们,我们找你”来总结IoC的思想,即由IoC容器帮对象找相应的依赖对象注入,而不是由对象主动去找依赖对象注入。

2. IoC的另一个角度

DI(Dependency Injection),翻译过来就是依赖注入,可以理解为从另一个角度的IoC思想,这里的依赖注入是指:

  • 依赖

应用程序依赖了IoC容器,应用程序需要IoC来提供对象需要的资源(包括依赖对象,配置等)

  • 注入

IoC容器注入应用程序需要的某个对象以及应用程序依赖的对象(实际还包含更多的外部资源,如配置等)。

3. BeanFactory和ApplicationContext

Spring的 org.springframework.beansorg.springframework.context 包是IoC的基础,其中:

  • BeanFactory 接口提供了IoC容器最基本功能
  • ApplicationContext 是BeanFactory的子接口,提供了更多的功能,包含SpringAOP,i18n国际化,事件,以及不同层次的context实现(如WebApplicationContext)

下面举例两者构建IoC容器的方式,这里使用XML直接开启注解的方式声明Bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="io.michong2022.spring.bean.demo"/>
</beans>

上面spring.xml的 <context:component-scan> 配置会启用 <context:annotation-config/>,而<context:annotation-config/> 会注册下面的Bean:

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor
  • EventListenerMethodProcessor

io.michong2022.spring.bean.demo 包中声明了两个Bean(UserService, UserAddressService):

/**
* @author 米虫2022
*/
@Service
public class UserService { @Autowired
private UserAddressService userAddressService;
}

BeanFactory构建IoC容器:

@Test
public void testBeanFactory() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions("spring.xml"); beanFactory.addBeanPostProcessor(beanFactory.getBean(AutowiredAnnotationBeanPostProcessor.class));
UserService userService = beanFactory.getBean(UserService.class);
Assertions.assertNotNull(userService);
}

ApplicationContext构建IoC容器:

@Test
public void testApplicationContext() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = context.getBean(UserService.class);
Assertions.assertNotNull(userService);
}

这里会加载7个Bean,2个自定义的,以及5个context:annotation-config注册的:

Loaded 7 bean definitions from class path resource [spring.xml]

默认情况下自定义的Bean是单例的:

Creating shared instance of singleton bean 'userService'

4. IoC Bean的构建

针对Ioc Bean的构建,BeanFactory和ApplicationContext的行为有些许不同,以ClassPathXmlApplicationContext为例,它会在refresh()的时候,

通过 finishBeanFactoryInitialization(beanFactory) 将Bean都初始化,而BeanFactory则在getBean()的时候才真正的创建Bean。

以单例Bean的构建为例,Bean构建流程如下:

其中:Bean单例对象由DefaultSingletonBeanRegistry的singletonObjects管理,DefaultListableBeanFactory的父类继承了DefaultSingletonBeanRegistry。

5. Bean的循环依赖

Bean的循环依赖,指Bean直接或间接的依赖构成了环,如:A -> B, B -> C, C -> A,下面是一个例子:

@Service
public class UserService {
// UserService依赖UserAddressService
@Autowired
private UserAddressService userAddressService;
} @Service
public class UserAddressService {
// UserAddressService也依赖UserService
@Autowired
private UserService userService;
}
  • Spring解决了单例Bean的Setter的循环依赖,对于构造函数循环依赖和非单例的Bean循环依赖会抛出异常。

DefaultSingletonBeanRegistry中有一些容器管理着Bean创建过程的状态:

  • singletonObjects: 创建成功的单例Bean对象缓存池
  • singletonFactories: 提前暴露的Bean对象工厂缓存池,工厂提供Bean完成了实例化,并没有完成依赖注入
  • earlySingletonObjects: 提前暴露的Bean对象缓存池
  • registeredSingletons: 已经注册的Bean名称
  • singletonsCurrentlyInCreation: 正在创建的Bean

Bean的依赖注入流程如下:

在流程图的黄色部分,Spring会根据下面的顺序获取依赖的Bean对象:

  1. 尝试从singletonObjects获取已经成功创建的Bean
  2. 如果(1)获取不到,判断需要获取的Bean是否处理构建中,即是否登记在singletonsCurrentlyInCreation
  3. 如果需要获取的Bean登记在singletonsCurrentlyInCreation中,那么尝试从earlySingletonObjects获取Bean
  4. 如果(3)获取不到,那么从singletonFactories获取Bean的工厂,获取Bean对象,将Bean对象放入earlySingletonObjects中,且将工厂从singletonFactories移除

注意:从Bean工厂获取的Bean,并不是完成注入的Bean对象。

按上面的流程,例子中,测试用例 beanFactory.getBean(UserService.class); 获取Bean执行的流程如下:

  1. 构建UserService,登记singletonsCurrentlyInCreationsingletonFactories
  2. 为UserService注入UserAddressService对象(UserAddressService此时没有创建,暂停执行步骤3.)
  3. 构建UserAddressService,登记singletonsCurrentlyInCreationsingletonFactories
  4. 为UserAddressService注入UserService对象,从singletonFactories获取UserService工厂,从而获取UserService对象(UserAddressService属性为空)放入earlySingletonObjects中,并将UserService的工厂从singletonFactories移除,将获取的UserService对象注入
  5. UserAddressService对象构建完成,将放入singletonObjects中,并将UserAddressService工厂从singletonFactories移除
  6. 完成Bean的构建流程,在afterSingletonCreation中从singletonsCurrentlyInCreation移除
  7. 继续步骤2.

下面是流程图黄色部分的源码实现:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}

不支持循环依赖的Bean构建:

  • 构造函数注入循环依赖,抛org.springframework.beans.factory.UnsatisfiedDependencyException异常
  • 非单例对象注入循环依赖,抛org.springframework.beans.factory.UnsatisfiedDependencyException异常

7. 自动装配

自动装配是指Spring自动的注入对象,Spring可以通过注解@Autowired来完成自动装配,默认情况下,@Autowired优先通过属性名称注入,即byName注入优先。

下面这个例子,自动装配会发生异常:

public interface Address {
} @Service("provinceAddress")
public class ProvinceAddress implements Address {
} @Service("cityAddress")
public class CityAddress implements Address {
} @Service
public class UserService {
@Autowired
private Address address;
}

出现如下异常:

No qualifying bean of type 'io.michong2022.spring.bean.demo.Address' available: expected single matching bean but found 2: cityAddress,provinceAddress

可以通过如下方式解决:

  1. 修改变量名address为想要注入的Bean名称,如cityAddress或provinceAddress
  2. 为CityAddress或ProvinceAddress加上@Primary注解,标注为默认的注入对象
  3. 给变量address加上@Qualifier("cityAddress")注解,指定注入的对象

8. Bean的生命周期

Spring Bean的声明周期如下:

  1. 构造函数
  2. BeanPostProcessor.postProcessBeforeInitialization()
  3. @PostConstruct (CommonAnnotationBeanPostProcessor)
  4. afterPropertiesSet (InitializingBean)
  5. init-method (Bean配置指定)
  6. BeanPostProcessor.postProcessAfterInitialization()
  7. @PreDestroy (CommonAnnotationBeanPostProcessor)
  8. destroy (DisposableBean)
  9. destroy-method (Bean配置指定)

最新文章

  1. 【转】iOS学习之Storyboard中的UIScrollView使用自动布局
  2. Java 开发环境的搭建
  3. 【日常小记】linux中强大且常用命令:find、grep【转】
  4. 【前台 submit的重复提交 错误】submit的重复提交
  5. 【POJ】2828 Buy Tickets(线段树+特殊的技巧/splay)
  6. POJ 1523 SPF(求割点)
  7. hdu1051 Wooden Sticks
  8. 51nod1417 天堂里的游戏
  9. SQL于union, EXCEPT 和 INTERSECT用法
  10. SQL Server 2008 R2 性能计数器详细列表(一)
  11. OD提示 &quot;为了执行系统不支持的动作, OllyICE 在这个被调试的程序中注入了一点代码, 但是经过5秒仍未收到响应...&quot; 解决办法
  12. 重拾java中的 i++ 和 ++i
  13. PowerShell 异常处理
  14. linear-grident的属性和使用以及对颜色后面参数(百分比)的理解
  15. c#Socket服务器与客户端的开发(2)
  16. Javascript与C#对变量的处理方式
  17. hdu 5877 Weak Pair (Treap)
  18. A - 不要62
  19. JMeter 脚本开发(五)
  20. InstallShield2015制作安装包----------安装过程中修改文件内容

热门文章

  1. NSIS 自定义安装界面准确获取安装进度完美解决方案
  2. 驱动开发:通过ReadFile与内核层通信
  3. Linux+Wine玩火影忍者究极风暴3指南
  4. Qemu/Limbo/KVM镜像:Ubuntu Mate 22.04+Wine 7.8
  5. Java程序设计(四)作业
  6. 12.MongoDB系列之副本集管理
  7. CURL提交--POST/GET-带header信息
  8. Tauri-Vue3桌面端聊天室|tauri+vite3仿微信|tauri聊天程序EXE
  9. 云原生之旅 - 4)基础设施即代码 使用 Terraform 创建 Kubernetes
  10. 关于网页实现串口或者TCP通讯的说明