共计 3035 个字符,预计需要花费 8 分钟才能阅读完成。
基于 Springframework 的利用开发,尤其在零碎比较复杂时,可能会呈现 Bean 循环援用的情景。本文分享一下如何解决这类问题。
关注公众号 逻魔代码 及时获取更多技术干货!
<!– more –>
失常援用依赖关系:
Bean A → Bean B → Bean C
循环援用依赖关系:
Bean A → Bean B → Bean A
咱们晓得 Spring 利用在启动时,即会创立 Spring context,加载并实例化 Bean。
失常援用依赖关系中,Spring 尝试实例化 A,发现其依赖 B,则会尝试实例化 B,又发现其依赖 C,则会尝试实例化 C。最终 Spring 会顺次创立 bean C,B,A。
而循环援用依赖关系中,Spring 尝试实例化 A,发现其依赖 B,则会尝试实例化 B,又发现其依赖 A,则会尝试实例化 A。最终,Spring 无奈决定到底该先实例化 A 还是先实例化 B。
Spring 在遇到循环援用时,会间接抛出 BeanCurrentlyInCreationException
异样,如:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanOne':
Requested bean is currently in creation: Is there an unresolvable circular reference?
来看一个应用应用结构器注入引发循环援用的例子:
@Component
public class BeanOne {
private final BeanTwo beanTwo;
public BeanOne(BeanTwo beanTwo) {this.beanTwo = beanTwo;}
}
@Component
public class BeanTwo {
private final BeanOne beanOne;
public BeanTwo(BeanOne beanOne) {this.beanOne = beanOne;}
}
BeanOne 和 BeanTwo 相互依赖。
解决循环援用,有多种办法:
- 应用 Field/Setter 注入
- 应用
@PostConstruct
注解 - 应用
@Lazy
注解
应用 Field/Setter 注入解决循环援用问题
最罕用的解决循环援用的办法,就是应用字段注入或者设置办法注入。
应用 Field 注入,批改代码,去掉依赖彼此的构造方法:
@Component
public class BeanOne {
@Autowired
private BeanTwo beanTwo;
}
@Component
public class BeanTwo {
@Autowired
private BeanOne beanOne;
}
相似的,应用 Setter 注入:
@Component
public class BeanOne {
private BeanTwo beanTwo;
@Autowired
public void setBeanTwo(BeanTwo beanTwo) {this.beanTwo = beanTwo;}
}
@Component
public class BeanTwo {
private BeanOne beanOne;
@Autowired
public void setBeanOne(BeanOne beanOne) {this.beanOne = beanOne;}
}
这两种形式解决思路是统一的,应用默认的无参结构器实例化 bean,此时无需保障其依赖的 bean 已被实例化。Field 注入实质上和 Setter 注入是一样的。
应用 @PostConstruct 注解
批改示例代码如下:
@Component
public class BeanOne {
private final BeanTwo beanTwo;
public BeanOne(BeanTwo beanTwo) {this.beanTwo = beanTwo;}
@PostConstruct
public void init() {beanTwo.setBeanOne(this);
}
}
@Component
public class BeanTwo {
private BeanOne beanOne;
public void setBeanOne(BeanOne beanOne) {this.beanOne = beanOne;}
}
可见,BeanOne 实例化时应用结构器注入 beanTwo,而 BeanTwo 实例化时则应用的是默认的无参结构器,没有依赖 beanOne 产生依赖。那么具体应用时,BeanTwo 的实例中,其 beanOne 属性为 null?并不是。留神到 BeanOne 中有一个应用 @PostConstruct 标注的 init() 办法,查看其该注解的源码正文:
The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization…
应用 @PostConstruct
标注的办法,会在依赖注入之后执行,用于某些初始化操作。在这里,这所谓的“初始化”操作,就是为 beanTwo 的 beanOne 属性赋值。
应用 @Lazy 注解
批改代码如下:
@Component
public class BeanOne {
private final BeanTwo beanTwo;
public BeanOne(BeanTwo beanTwo) {this.beanTwo = beanTwo;}
}
@Component
public class BeanTwo {
private final BeanOne beanOne;
@Lazy
public BeanTwo(BeanOne beanOne) {this.beanOne = beanOne;}
}
留神到,批改后的代码,与一开始的引发循环援用异样的代码简直完全相同,差别仅在于 BeanTwo 构造方法下面的 @Lazy
注解。顾名思义,该注解表明这个结构注入懒执行。
查看其源码正文:
Indicates whether a bean is to be lazily initialized.
查看其源码:
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
可见 @Lazy
注解,标示一个 Bean 是否被懒初始化。该注解可用于 类型、办法、结构器、参数、字段等指标,在本例中,用在结构器上,示意只有须要用到该类实例时才进行调用该结构器进行实例化操作。所以在该实例中,可能的实例化过程如下:
剖析过程:
Start: 尝试实例化 beanOne → 发现须要依赖 beanTwo → 尝试实例化 beanTwo
Start: 尝试实例化 beanTwo → 发现 @Lazy 注解,暂不实例化;
实例化过程:
实例化 beanTwo → 实例化 beanOne
简言之,应用 @Lazy
标注的类,不会在容器中被动触发实例化,只有当被应用到 / 被依赖到时,被动触发实例化。
综上,介绍了三种(或四种)解决 Spring 利用中循环援用的计划,并没有优劣之分,能够依据本人的爱好自由选择。此外,@Lazy
注解还有很多细节原理能够开掘,且再觅机会介绍了。
具体的代码实现细节,请参考源码 Demo Source Code
关注公众号 逻魔代码 及时获取更多技术干货!
本文由 mdnice 多平台公布