Spring 解析 Bean 之间的依赖关系并自动注入依赖,是 Spring IoC 容器的核心功能。这个过程可以分为几个关键步骤,涉及 BeanDefinition 的解析、依赖关系的识别、依赖对象的查找和创建、以及依赖的注入:
1. BeanDefinition 的解析和注册:
- 扫描和解析: Spring 容器启动时,会扫描指定的包(通过
<context:component-scan>、@ComponentScan或 JavaConfig 中的@Configuration类)或读取 XML 配置文件。 - BeanDefinition 创建: 对于每个符合条件的类(例如带有
@Component、@Service、@Repository、@Controller等注解的类,或 XML 中配置的<bean>元素),Spring 会创建一个BeanDefinition对象。BeanDefinition包含了 Bean 的所有元数据信息,例如:- Bean 的类名 (Class Name)
- Bean 的作用域 (Scope,如 singleton、prototype)
- Bean 的构造方法参数 (Constructor Arguments)
- Bean 的属性 (Properties)
- Bean 的初始化方法 (Init Method)
- Bean 的销毁方法 (Destroy Method)
- Bean 的依赖关系 (Dependencies)
- Bean 是否延迟初始化 (Lazy Init)
- …等等
- BeanDefinition 注册: Spring 将创建好的
BeanDefinition对象注册到BeanDefinitionRegistry中(通常是DefaultListableBeanFactory)。BeanDefinitionRegistry是一个 Map 结构,以 Bean 的名称作为 key,BeanDefinition作为 value。
2. 依赖关系的识别:
Spring 通过以下几种方式识别 Bean 之间的依赖关系:
- 构造器注入:
- XML 配置: Spring 解析
<constructor-arg>标签,获取依赖 Bean 的名称或类型。 - 注解: Spring 检测构造方法上的
@Autowired注解(或隐式构造方法注入),根据构造方法参数的类型来确定依赖。 - JavaConfig: Spring 解析
@Bean方法的参数,根据参数类型确定依赖。
- XML 配置: Spring 解析
- Setter 方法注入:
- XML 配置: Spring 解析
<property>标签,获取依赖 Bean 的名称或类型。 - 注解: Spring 检测 setter 方法上的
@Autowired注解,根据 setter 方法参数的类型来确定依赖。 - JavaConfig: Spring 在创建 Bean 实例后,会调用相应的 setter 方法来注入依赖。
- XML 配置: Spring 解析
- 字段注入:
- 注解: Spring 检测字段上的
@Autowired注解,根据字段的类型来确定依赖。
- 注解: Spring 检测字段上的
- @Resource:
- 注解: 优先按照名字注入,如果没有找到,再按照类型。
- 接口注入
- 如果实现类只有一个,那么直接注入。
- 如果一个接口有多个实现类, 可以通过
@Qualifier或者@Primary等注解来指定具体注入哪一个。
3. 依赖对象的查找和创建:
当 Spring 需要为一个 Bean 注入依赖时,它会执行以下步骤:
- 按名称查找: 如果依赖是通过名称指定的(例如
<property name="xxx" ref="yyy"/>或@Qualifier("yyy")),Spring 会直接从BeanDefinitionRegistry或已创建的 Bean 实例中查找名为 “yyy” 的 Bean。 - 按类型查找: 如果依赖是通过类型指定的(例如构造方法参数、setter 方法参数、字段类型),Spring 会从
BeanDefinitionRegistry或已创建的 Bean 实例中查找与该类型匹配的 Bean。- 多个匹配项: 如果找到多个匹配类型的 Bean,Spring 会根据以下规则进行处理:
@Primary注解: 如果其中一个 Bean 标记了@Primary注解,Spring 会选择该 Bean。@Qualifier注解: 如果注入点使用了@Qualifier注解指定了 Bean 的名称,Spring 会选择该 Bean。- 类型匹配: 如果没有
@Primary和@Qualifier注解,Spring 会尝试根据字段名、setter方法名、构造器参数名 与 Bean 名称 进行匹配。 - 抛出异常: 如果以上规则都无法确定唯一的 Bean,Spring 会抛出
NoUniqueBeanDefinitionException异常。
- 多个匹配项: 如果找到多个匹配类型的 Bean,Spring 会根据以下规则进行处理:
- 创建依赖对象: 如果容器中没有找到所需的依赖对象,Spring 会根据该依赖对象的
BeanDefinition创建一个新的实例(递归地进行依赖注入)。
4. 依赖的注入:
找到依赖对象后,Spring 会通过以下方式将依赖注入到目标 Bean 中:
- 构造器注入: 通过调用目标 Bean 的构造方法,将依赖对象作为参数传入。
- Setter 方法注入: 通过调用目标 Bean 的 setter 方法,将依赖对象作为参数传入。
- 字段注入: 通过反射,直接将依赖对象设置到目标 Bean 的字段中。
5. 循环依赖的处理(简化版):
Spring 通过“三级缓存”机制解决 Setter 方法注入和字段注入的循环依赖(构造器注入的循环依赖无法解决):
- 一级缓存(singletonObjects): 存放完全初始化好的单例 Bean。
- 二级缓存(earlySingletonObjects): 存放早期暴露的 Bean 实例(已创建但未完全初始化,属性可能还未注入)。
- 三级缓存(singletonFactories): 存放创建 Bean 实例的
ObjectFactory。
当 A 依赖 B,B 依赖 A 时:
- 创建 A 实例,将 A 的
ObjectFactory放入三级缓存,并将一个早期的 A 实例(未完成属性注入)放入二级缓存。 - A 注入 B,发现 B 不存在,开始创建 B。
- 创建 B 实例,将 B 的
ObjectFactory放入三级缓存,并将一个早期的 B 实例放入二级缓存。 - B 注入 A,从二级缓存中获取早期的 A 实例(虽然 A 还未完全初始化,但 B 可以先持有 A 的引用)。
- B 完成初始化,放入一级缓存。
- A 继续初始化,注入 B(从一级缓存中获取),完成初始化,放入一级缓存。
总结:
Spring 依赖注入的实现是一个复杂而精细的过程,它涉及 BeanDefinition 的解析、依赖关系的识别、依赖对象的查找和创建、依赖的注入、以及循环依赖的处理等多个步骤。Spring 通过反射、注解、XML 解析等技术,实现了自动化、灵活的依赖注入,大大简化了 Spring 应用程序的开发。
1117

被折叠的 条评论
为什么被折叠?



