IoC,Inversion of Control 控制反转,将原本由应用程序负责对象创建的工作,交给IOC容器来完成。容器通过依赖注入(DI,Dependency Injection)来实现。
作用:降低类对象之间的耦合度,减少代码量。
1 Ioc 容器
图 IoC容器的工作流程
- 定义Bean:通过XML、Java注解或Java配置类来定义。
- 解析与存储BeanDefinition:容器读取Bean的配置,解析为BeanDefinition对象,存储到BeanFactory中。
- 创建Bean:根据BeanDefinition创建Bean实例,注入Bean的依赖。
- 调用生命周期方法:Bean初始化时会调用这个Bean实现的初始化方法(如@PostConstruct)。容器关闭时会调用Bean实现的销毁方法(如@PreDestory)。
BeanFactory 是提供了管理Bean方法的接口。ApplicationContext继承了BeanFactory接口,并做了扩展:1)集成AOP。2)国际化。3)事件发布。4)资源加载ResourceLoader。
2 定义Bean
Bean是由Ioc容器管理的对象。
singleton | 单例,默认。同一个容器只有一个实例,不同容器生成不同的bean实例。 |
prototype | 不同bean依赖的bean会被创建不同的实例。 |
request | 每个请求都会创建不同的bean。 |
session | 每个session都会创建不同的bean。 |
application | 每个servletContext生成不同的bean。 |
websocket | 每个socket连接生成不同的bean。 |
表 Bean的作用域
定义方式 | 优点 | 缺点 |
XML | 易于阅读和编辑,可扩展性强。 | 不够简洁,代码耦合度高。 |
注解 | 简洁易读,易于维护。 | 破坏了类的纯洁性,不易调试。 |
表 XML与注解形式定义Bean的优缺点
3 解析
容器从XML或注解中读取Bean的配置信息,并将其转换成BeanDefinition对象。
BeanDefinitionReader是定义了读取Bean配置方法的接口,XmlBeanDefinitionReader是其一个实现类,用于解析并读取XML配置的Bean信息。
容器会将BeanDefinition对象存储到容器的注册表中,为后续Bean的实例化和依赖注入提供依据。
4 Bean的实例化
构造器注入 | 推荐首选方式,通过构造函数传递依赖项。依赖不可变。 |
Setter方法注入 | 通过Setter方法设置依赖项,适用可选依赖或需要重新配置的依赖。 |
字段注入 | 不推荐,直接通过字段注入依赖项(@Autowired直接加在字段上面)。 |
表 Bean的依赖注入方式
4.1 循环依赖及三级缓存
循环依赖是指有两个bean,它们有字段互相指向对方。构造器注入在面对循环依赖时会在项目启动时抛错。而字段注入依靠“三级缓存”技术则解决了这个问题。
三级缓存通过提前暴露Bean的早期引用,来打破循环依赖的死锁。
三级缓存 | 存储Bean的工厂对象(ObjectFacotry)。 |
二级缓存 | 存储由三级缓存提供的工厂对象实例化的Bean,此时还未完成依赖注入。 |
一级缓存 | 存储完全初始化好的单例Bean。 |
表 三级缓存的组成
循环依赖解决流程如下(以Bean A 依赖Bean B,B依赖A为例):
1)创建Bean A。
a)实例化A,获得A的原始对象(通过反射调用无参构造器)。
b)将A的工作对象放入三级缓存中。
addSingletonFactory(beanNameA,() -> getEarlyBeanReference(beanNameA,mdb,beanA));
c)填充A的属性,发现A依赖Bean B,触发Bean B的创建。
2)创建Bean B。
前两步同Bean A的创建,然后再填充B的属性,发现B依赖A,触发Bean A的获取。
3)解决B对A的依赖。
a)从一级缓存获取A,未找到。
b)从二级缓存获取A,未找到。
c)从三级缓存获取A的工厂对象,找到并调用其getObject()生成A的早期引用。
d)将A的早期引用存入二级缓存,并从三级缓存移除A的工厂对象。
e)将A到早期引用注入到B中。
f)完成Bean B的初始化,并将其放入一级缓存。
4)完成A的初始化。
a)将Bean B的引用注入到A中。
b)完成Bean A的初始化,将A放入一级缓存,并从二级缓存移除。
4.1.1 工厂对象ObjectFactory
@FunctionalInterface
public interface ObjectFactory<T> {T getObject() throws BeansException;
}
通过getObject()方法延迟生成或获取一个对象。(@FunctionalInterface 是一个函数式接口,可通过Lambda表达式实现。),有以下作用:
- 延迟创建对象,只有在调用getObject() 是才会实际生成或获取对象。
- 处理代理逻辑,如AOP增强Bean。
- 解耦实例化过程:将对象生成逻辑封装在工厂中,避免直接暴露未完成的Bean。
4.1.2 第二级缓存的必要性
第二级缓存用于隔离“完全初始化”和“未完全初始化”的Bean。确保外部组件只能获取完全初始化的Bean(只能通过一级缓存),而二级缓存仅用于内部依赖注入。
4.2 字段注入的缺陷
1)破坏了封装性,直接注入私有字段,绕过构造器及Setters,通过反射注入。违反了面向对象的设计原则。
2)循环依赖的潜在风险。
3)字段注入的依赖可变,而构造器注入的依赖是final的。
5 生命周期
当容器初始化bean后会调用bean实现的InitializingBean接口的afterPropertiesSeet()方法,当容器销毁时,会调用bean实现的DisposableBean接口的destory()方法。
BeanPostProcessor接口提供了可以覆盖容器实例化逻辑或依赖逻辑的方法。bean 先由容器实例化,然后由BeanPostProcessor进行操作。