网站备案在哪查,八面通网站建设,杭州专业网站,互联网营销师题库文章目录 1 Bean 创建流程1.1 Bean的扫描注册1.2 创建Bean的顺序 2 三种Bean注入方式2.1 构造器注入 | Constructor Injection#xff08;推荐#xff09;2.2 字段注入 | Field Injection#xff08;常用#xff09;2.3 方法注入 | Setter Injection2.4 三种方式注入顺序 3… 文章目录 1 Bean 创建流程1.1 Bean的扫描注册1.2 创建Bean的顺序 2 三种Bean注入方式2.1 构造器注入 | Constructor Injection推荐2.2 字段注入 | Field Injection常用2.3 方法注入 | Setter Injection2.4 三种方式注入顺序 3 循环依赖3.1 构造器注入3.2 字段/setter注入3.3 乱想构造器字段3.4 解决方案 1 Bean 创建流程
简单来说当容器里要放的Bean很多时Spring会优先创建依赖最少的Bean。
1.1 Bean的扫描注册
Spring启动后首先会根据SpringbootApplication的包扫描配置扫描包里的所有文件然后将使用了注解标记的类如Component、Service、Repository、Configuration作为组件注册到上下文中同时解析这些组件之间的依赖关系。
1.2 创建Bean的顺序
组件之间的依赖关系通常会使用图/树结构来表示如果BeanA依赖BeanB那么BeanA是BeanB的父节点。在创建Bean的时候Spring会优先选择树的叶子结点进行创建因为它不存在依赖然后再不断向上层进行创建也就是自底向上创建这样才能尽量确保在创建某个Bean时它依赖的Bean已经存在。
在创建Bean的时候Spring仍会检查它需要的依赖是否已经存在如果存在则直接注入如果依赖Bean还没创建那么会去递归创建依赖的Bean直到所有依赖都被创建再创建当前Bean。
2 三种Bean注入方式
2.1 构造器注入 | Constructor Injection推荐
构造器注入是在组件的构造函数中注入所需的依赖它是在Bean创建时就注入依赖创建流程如1.2。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;Component
public class BeanA {private final BeanB beanB;private final BeanC beanC;public BeanA(BeanB beanB, BeanC beanC) {this.beanB beanB;this.beanC beanC;}// 其他方法...
}Component
public class BeanB {// 其他方法...
}Component
public class BeanC {// 其他方法...
}使用构造器进行依赖注入时依赖的对象通常会被声明为final这样当对象创建后依赖的Bean不会被改变可以保证类的一致性。
这样注入的优势是能使Bean之间的依赖关系更加清楚避免了字段注入可能存在的隐式依赖如果存在问题比如循环依赖会在Spring初始化时就抛出异常而不会等到执行时才出错。
需要注意的是不能显示提供无参构造函数否则Spring会优先执行无参构造导致所有依赖的Bean都为null如果有多个构造函数选择一个使用Autowired注解否则可能报错。
2.2 字段注入 | Field Injection常用
字段注入就是使用Autowired注解自动注入依赖的Bean。它不会在构造函数中注入而是通过反射在组件构造函数执行后才注入依赖Bean即Bean实例化完成后才注入依赖项因此不能使用final修饰依赖Bean因为使用final字段修饰的变量必须在声明时或在构造函数中初始化而字段注入在构造函数之后执行。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;Component
public class BeanA {Autowiredprivate BeanB beanB;
}Component
public class BeanB {// 其他方法...
}字段注入是开发时最常用的方式但由于字段注入是在Bean实例后才注入属于隐式依赖所以可能会存在空指针问题而这个问题只有当程序运行时才出现因此有一定隐患所以Spring官方更推荐构造器注入。
2.3 方法注入 | Setter Injection
和字段注入类似只是需要写好一个setter函数在setter中注入依赖一个setter方法通常对应一个依赖Autowired注解写在setter方法上
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;Component
public class BeanA {private final BeanB beanB;private final BeanC beanC;Autowiredpublic setBeanB(BeanB beanB) {this.beanB beanB;}Autowiredpublic setBeanC(BeanC beanC) {this.beanC beanC;}
setter注入的优势是可以灵活注入bean相比构造器一次性写入更加清晰一些缺点和字段注入类似。
2.4 三种方式注入顺序
如果一个组件中同时存在以上三种注入方式执行顺序是
按照构造器注入–字段注入–方法注入的原则执行。
首先执行构造函数注入在构造函数初始化的Bean。构造函数执行结束后Spring将处理字段注入然后在容器中查找并注入依赖Bean。最后如果存在带有Autowired注解的setter方法Spring会再调用这些方法注入依赖。
3 循环依赖
循环依赖指的是两个Bean之间互相需要对方作为成员变量如BeanA需要注入BeanBBeanB需要注入BeanA。
3.1 构造器注入
构造器注入时会通过构造函数注入所有必须的依赖当两个组件BeanA和BeanB之间存在循坏依赖时执行BeanA的构造函数需要注入BeanB此时BeanA还未创建由于BeanB还未生成因此转而先创建BeanB执行BeanB的构造函数而BeanB同样需要注入BeanA于是出现了死锁情况两个Bean都无法创建因此如果使用构造器注入而又出现循环依赖时Spring会直接抛出BeanCurrentlyInCreationException异常。
3.2 字段/setter注入
使用字段/setter注入在循环依赖时不会抛出异常但很容易出问题主要是通过提前暴露Bean的方式来解决的。
因为在循环依赖的情况下字段/setter注入会先创建当前对象的“代理”或“占位符”实例/引用非完全实例但可以拿来使用然后通过反射等依赖对象存在后再注入依赖因此不会在创建时出现死锁没有循环等待而构造器注入必须注入完全初始化的依赖后才能实例化因此会死锁。
以BeanA/B为例
当容器尝试创建BeanA时候发现它依赖BeanB然后转而去创建BeanB。创建BeanB时发现其依赖于BeanA循环依赖出现因此会在单例池中创建一个BeanA的占位符实例未初始化。BeanB创建后Spring会将BeanA的占位符注入到BeanB中此时BeanB完成注入它的实例也创建完毕将被放入单例池。返回BeanA的创建此时BeanA已经有占位符实例BeanB也有实例因此可以将BeanB注入BeanA中。BeanA和BeanB都完成初始化。
占位符实例并不是在 BeanA 开始创建时就生成的而是依赖关系的解析过程中当需要 BeanA 时才创建。因为当Spring开始创建一个Bean的时候会标记当前Bean为“正在创建”如果在它的实例化过程中递归注入依赖发现有其他对象请求该bean则证明循环依赖出现Spring正是通过这种动态追踪的方式来识别循环依赖的。
如果不存在循环依赖BeanB已经实例化那么会被直接注入BeanA这样BeanA也会直接完成初始化实例。没有循环依赖不会生成占位符实例。
3.3 乱想构造器字段
想到了一个组合如果BeanA使用构造器注入BeanB而BeanB使用字段注入注入BeanA那么能否通过占位符实例实现注入呢
答案是不行原因主要有三点
构造器注入要求注入的依赖必须是完全初始化的实例【核心】。构造器注入时在循环依赖情况下被动生成的占位符实例不允许使用因为构造函数不允许注入未完全实例化的对象本质上与第二点一样。构造器注入不会像字段注入那样生成占位符实例因为就算生成了也不完全无法使用
因此无论是先创建BeanA还是先创建BeanB都会抛出异常。
先创建BeanA发现依赖BeanB转而创建BeanB又发现BeanB依赖BeanA因此尝试创建BeanA的占位符实例但是因为A是构造器注入必须注入BeanB的完整实例但并不存在因此不允许使用占位符实例失败。
先创建BeanB发现依赖BeanA转而创建BeanABeanA必须使用完全实例化的BeanB不会创建BeanB的占位符实例因此无法达成失败。
3.4 解决方案
一般情况下需要避免循环依赖如果存在可以尝试将一些依赖关系移除重构依赖关系降低耦合。或者可以使用Lazy注解延迟Bean的加载懒加载可以让Bean在被使用时才注入。
Component
public class BeanA {AutowiredLazyprivate BeanB beanB; // 延迟注入// 其他方法...
}Component
public class BeanB {Autowiredprivate BeanA beanA; // 直接注入// 其他方法...
}