在官网中,咱们发现它的核心技术之一:Dependency Injection
,简称:DI
,翻译过来就是依赖注入。今天咱们就来盘一盘它。java
在本文中,咱们将深刻研究 Spring
框架 DI
背后的故事,包括 Spring Inversion of Control
(控制反转)、 DI
和 ApplicationContext
接口。 基于这些基本概念,咱们将研究如何使用基于 java
和基于 XML
的配置来 建立Spring
应用程序。 最后,咱们将探讨在建立 Spring
应用程序时遇到的一些常见问题,包括 bean冲突和循环依赖性。算法
在学习DI
以前,咱们先学习一下 IoC
(控制反转),接下来的一段可能读起来会让你感受比较啰嗦,可是要细细体会每一次改变的意图,和咱们的解决方案,对于理解控制反转很是重要。spring
首先来了解下咱们一般实例化一个对象的方式。 在 平时,咱们使用 new
关键字实例化一个对象。 例如,若是有一个 Car
类,咱们可使用如下方法实例化一个对象 Car
express
Car car = new Car();
复制代码
由于汽车有不少零部件组成,咱们定义Engine
接口来模拟汽车引擎,而后将engine
对象做为成员变量放在Car
类markdown
public interface Engine {
void turnOn();
}
public class Car {
private Engine engine;
public Car() {}
public void start() {
engine.turnOn();
}
}
复制代码
如今,咱们能够调用start()方法吗?显然是不行的,一眼能够看出会报NullPointerException (NPE)
,由于咱们没有在Car
的构造函数中初始化engine
。一般咱们采用的方案就是在Car的构造函数中以为使用Engine
接口的哪一个实现,并直接将该实现分配给engine
字段;框架
如今,咱们来首先建立Engine
接口的实现类dom
public class ElectricEngine implements Engine {
@Override
public void turnOn() {
System.out.println("电动引擎启动");
}
}
public class CombustionEngine implements Engine {
@Override
public void turnOn() {
System.out.println("燃油引擎启动");
}
}
复制代码
咱们修改Car的构造函数,使用ElectricEngine
实现,将咱们的engine
字段分配给一个实例化的ElectricEngine
对象ide
public class Car {
private Engine engine;
public Car() {
this.engine = new ElectricEngine();
}
public void start() {
engine.turnOn();
}
public static void main(String[] args) {
Car car = new Car();
car.start();
}
}
复制代码
如今咱们执行start()
方法,咱们会看到以下输出:函数
大功告成,咱们成功解决了 NPE
(空指针)问题,可是咱们胜利了吗?哈哈哈,显然没有!学习
在解决问题的同时,咱们又引入了另外一个问题。尽管咱们经过抽象Engine
接口,而后经过不一样的Engine
实现类来负责不一样类型引擎的业务逻辑,的确是很好的设计策略。可是细心的伙伴可能已经发现了,咱们Car
类的构造函数中将engine
声明为CombustionEngine
,这将致使全部车都有一个燃油引擎。假如咱们如今要建立不一样的汽车对象,它有一个电动引擎,咱们将不得不改变咱们的设计。比较常见的方法是建立两个独立里的类,各司其职,在他们的构造函数中将engine
分配给Engine
接口的不一样实现;
例如:
public class CombustionCar {
private Engine engine;
public CombustionCar() {
this.engine = new CombustionEngine();
}
public void start() {
engine.turnOn();
}
}
public class ElectricCar {
private Engine engine;
public ElectricCar() {
this.engine = new ElectricEngine();
}
public void start() {
engine.turnOn();
}
}
复制代码
经过上面的一顿骚操做,咱们成功的解决了咱们引擎的问题。若是是一个平常需求,咱们已经能够成功交工了。可是这显然不是我写这篇文章的目的。
从设计的角度来讲,目前的代码是糟糕的,有如下两点缘由:
start()
方法;Engine
实现类建立一个新的类;尤为后一个问题更加难以解决,由于咱们不控制Engine
的实现,随着开发人员不断的建立本身的实现类,这个问题会更加恶化;
带着上面的问题,咱们继续思考.....................
咱们能够建立一个父类Car
,将公共代码抽取到父类中,能够轻松解决第一个问题。因为Engine
字段是私有的,咱们在父类Car
的构造函数中接收Engine
对象,而且进行赋值。
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.turnOn();
}
}
public class CombustionCar extends Car{
public CombustionCar() {
super(new CombustionEngine());
}
}
public class ElectricCar extends Car {
public ElectricCar() {
super(new ElectricEngine());
}
}
复制代码
经过这种方法,咱们成功的解决了代码重复的问题,咱们来测试一下:
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.turnOn();
}
public static void main(String[] args) {
CombustionCar combustionCar1 = new CombustionCar();
combustionCar1.start();
ElectricCar electricCar1 = new ElectricCar();
electricCar1.start();
}
}
复制代码
那么咱们该如何解决咱们提出的第二个问题那?
其实这个问题咱们能够换个角度看:为何咱们要去关注CombustionCar
和ElectricCar
,咱们如今将关注点回到咱们的Car
,咱们如今已经容许客户端实例化Car
对象时候将Engine
对象做为构造函数的参数传入,其实已经消除了为每一个Engine
对象建立新Car
的问题。由于如今Car
类依赖于Engine
接口,并不知道任何Engine
的实现;
经过带有Engine
参数的构造函数,咱们已将要使用哪一个Engine
实现的决定从Car
类自己(最初由CombustionEngine
决定)更改成实例化Car
类的客户端。 决策过程的这种逆转称为IoC原则
。 如今,由客户端控制使用哪一种实现,而不是由Car
类自己控制使用哪一种Engine
实现。
有点绕,你们结合下面的示例代码,细细琢磨
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.turnOn();
}
public static void main(String[] args) {
/** * 老法子 * 为每一类型发送机的车建立类,而后实现父类car,而后在构造函数传入本身的引擎,而后调用start() */
CombustionCar combustionCar1 = new CombustionCar();
combustionCar1.start();
ElectricCar electricCar1 = new ElectricCar();
electricCar1.start();
/** * 控制反转思想 * 把本身看做实例化car的客户端,须要什么引擎,直接传入相关对象 */
CombustionEngine combustionEngine = new CombustionEngine();
Car combustionCar = new Car(combustionEngine);
combustionCar.start();
ElectricEngine electricEngine = new ElectricEngine();
Car electricCar = new Car(electricEngine);
electricCar.start();
}
}
复制代码
执行上面的代码,咱们发现均可以得到咱们想要的结果:
从上面的例子咱们能够看到,实例化Car
类的客户端能够控制所使用的Engine
实现,而且取决于将哪一个Engine
实现传递给Car
构造函数,Car
对象的行为发生巨大变化。为何这么说,接着看下面
在上面控制反转的知识点,咱们已经解决了由谁决定使用哪一种Engine
实现的问题,可是不可避免,咱们也更改了实例化一个Car
对象的步骤;
最开始,咱们实例化Car
不须要参数,由于在它的构造函数里面已经为咱们new
了Engine
对象。使用IoC
方法以后,咱们要求在实例化一个Car
以前,咱们须要先建立一个Engine
对象,并做为参数传递给Car
构造对象。换句话说,最初,咱们首先实例化Car
对象,而后实例化Engine
对象。可是,使用IoC
以后,咱们首先实例化Engine
对象,而后实例化Car
对象;
所以,咱们在上面的过程当中建立了一个依赖关系。不过这种依赖关系不是指编译时候Car
类对Engine
接口的依赖关系,相反,咱们引入了一个运行时依赖关系。在运行时,实例化Car
对象以前,必须首先实例化Engine
对象。
某一个具体的依赖对象你们能够理解为Spring中的bean,对于两个有依赖关系的bean,其中被依赖的那个bean,咱们把它称为依赖对象
咱们用图形化的方式来看看它们之间的依赖关系,其中图形的节点表明对象,箭头表明依赖关系(箭头指向依赖对象)。对于咱们个人Car
类,依赖关系树很是简单:
若是依赖关系树的终端结点还有本身的附加依赖关系,那么这个依赖关系树将变得更加复杂。如今再看咱们上面的例子,若是CombustionEngine
还有其余依赖对象,咱们首先须要建立CombustionEngine
的依赖对象,而后才能实例化一个CombustionEngine
对象。这样在建立Car
对象时候,才能将CombustionEngine
传递给Car
的构造函数;
//凸轮轴
public class Camshaft {}
//机轴
public class Crankshaft {}
public class CombustionEngine implements Engine {
//凸轮轴
private Camshaft camshaft;
//机轴
private Crankshaft crankshaft;
public CombustionEngine(Camshaft camshaft, Crankshaft crankshaft) {
this.camshaft = camshaft;
this.crankshaft = crankshaft;
}
@Override
public void turnOn() {
System.out.println("燃油引擎启动");
}
}
复制代码
通过咱们改造,咱们如今的依赖关系树变为下面的样子
随着咱们不断引入更多的依赖关系,这种复杂性将继续增加。为了解决这个复杂问题,咱们须要基于依赖关系树抽取对象的建立过程。这就是依赖注入框架。
通常来讲,咱们能够把这个过程分为三个部分:
经过反射,咱们能够查看 Car
类的构造函数,而且知道它须要一个 Engine
参数。所以为了建立Car对象,咱们必须建立至少一个Engine
接口的实现类用做依赖项来使用。在这里,咱们建立一个CombustionEngine
对象(为了方便,暂时当作只有一个实现类,bean冲突问题待会再说)来声明它做为依赖项来使用,就知足Car
对象建立时的需求.
其实,这个过程是递归的,由于CombustionEngine
依赖于其余对象,咱们须要不断重复第一个过程,直到把全部依赖对象声明完毕,而后注册建立这些依赖对象所须要的类。
第三点其实就是将前面两点思想付诸实施,从而造成一种建立对象的机制
举个例子:好比咱们须要一个Car
对象,咱们必须遍历依赖关系树并检查是否存在至少一个符合条件的类来知足全部依赖关系。 例如,声明CombustionEngine
类可知足Engine
节点要求。 若是存在这种依赖关系,咱们将实例化该依赖关系,而后移至下一个节点。
若是有一个以上的类知足所需的依赖关系,那么咱们必须显式声明应该选择哪种依赖关系。 稍后咱们将讨论 Spring 是如何作到这一点的。
一旦咱们肯定全部的依赖关系都准备好了,咱们就能够从终端节点开始建立依赖对象。 对于 Car
对象,咱们首先实例化 Camshaft
和Crankshaft
ーー由于这些对象没有依赖关系ーー而后将这些对象传递给 CombustionEngine
构造函数,以实例化 CombunstionEngine
对象。 最后,咱们将 CombunstionEngine
对象传递给 Car
构造函数,以实例化所需的 Car
对象。
了解了 DI
的基本原理以后,咱们如今能够继续讨论 Spring
如何执行 DI
。
Spring
的核心是一个DI
框架,它能够将DI
配置转换为Java
应用程序。
在这里咱们要阐述一个问题:那就是库和框架的区别。库只是类定义的集合。背后的缘由仅仅是代码重用,即获取其余开发人员已经编写的代码。这些类和方法一般在域特定区域中定义特定操做。例如,有一些数学库可以让开发人员仅调用函数而无需重作算法工做原理的实现。
框架一般被认为是一个骨架,咱们在其中插入代码以建立应用程序。 许多框架保留了特定于应用程序的部分,并要求咱们开发人员提供适合框架的代码。 在实践中,这意味着编写接口的实现,而后在框架中注册实现。
在 Spring
中,框架围绕 ApplicationContext
接口实现上一节中概述的三个 DI
职责。一般这个接口表明了一个上下文。 所以,咱们经过基于 java
或基于 xml
的配置向 ApplicationContext
注册合适的类,并从 ApplicationContext
请求建立 bean
对象。 而后 ApplicationContext
构建一个依赖关系树并遍历它以建立所需的 bean
对象
Applicationcontext
中包含的逻辑一般被称为 Spring
容器。 一般,一个 Spring
应用程序能够有多个 ApplicationContext
,每一个 ApplicationContext
能够有单独的配置。 例如,一个 ApplicationContext
可能被配置为使用 CombustionEngine
做为其引擎实现,而另外一个容器可能被配置为使用 ElectricEngine
做为其实现。
在本文中,咱们将重点讨论每一个应用程序的单个 ApplicationContext
,可是下面描述的概念即便在一个应用程序有多个 ApplicationContext
实例时也适用。
Spring为咱们提供了两种基于 java
的配置方式
基于java
的基本配置的核心,实际上是下面两个注解:
@Configuration
: 定义配置类@Bean
: 建立一个bean
例如,给出咱们以前定义的 Car
, CombustionEngine
, Camshaft
, 和Crankshaft
类,咱们能够建立一个下面 的配置类:
/** * @author milogenius * @date 2020/5/17 20:52 */
@Configuration
public class AnnotationConfig {
@Bean
public Car car(Engine engine) {
return new Car(engine);
}
@Bean
public Engine engine(Camshaft camshaft, Crankshaft crankshaft) {
return new CombustionEngine(camshaft, crankshaft);
}
@Bean
public Camshaft camshaft() {
return new Camshaft();
}
@Bean
public Crankshaft crankshaft() {
return new Crankshaft();
}
}
复制代码
接下来,咱们建立一个 ApplicationContext
对象,从 ApplicationContext
对象获取一个 Car
对象,而后在建立的 Car
对象上调用 start
方法:
ApplicationContext context =
new AnnotationConfigApplicationContext(AnnotationConfig.class);
Car car = context.getBean(Car.class);
car.start();
复制代码
执行结果以下:
Started combustion engine
复制代码
虽然@Configuration
和@Bean
注解的组合为 Spring
提供了足够的信息来执行依赖注入,但咱们仍然须要手动手动定义每一个将被注入的 bean
,并显式地声明它们的依赖关系。 为了减小配置 DI
框架所需的开销,Spring 提供了基于java
的自动配置。
为了支持基于 java
的自动配置,Spring
提供了额外的注解。 虽然咱们平时可能加过不少这种类型的注解,可是有三个最基本的注解:
@Component
: 注册为由 Spring 管理的类@Autowired
: 指示 Spring 注入一个依赖对象@ComponentScan
: 指示Spring在何处查找带有@Component
注解的类@Autowired
注解用来指导 Spring
,咱们打算在使用注解的位置注入一个依赖对象。 例如,在 Car
构造函数中,咱们指望注入一个 Engine
对象,所以,咱们给 Car
构造函数添加@Autowired
注解。 经过使用@Component
和@Autowired
注解改造咱们Car
类,以下所示:
@Component
public class Car {
private Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.turnOn();
}
}
复制代码
咱们能够在其余类中重复这个过程:
@Component
public class Camshaft {}
@Component
public class Crankshaft {}
@Component
public class CombustionEngine implements Engine {
private Camshaft camshaft;
private Crankshaft crankshaft;
@Autowired
public CombustionEngine(Camshaft camshaft, Crankshaft crankshaft) {
this.camshaft = camshaft;
this.crankshaft = crankshaft;
}
@Override
public void turnOn() {
System.out.println("Started combustion engine");
}
}
复制代码
改造完成相关类以后,咱们须要建立一个@Configuration
类来指导 Spring
如何自动配置咱们的应用程序。 对于基于 java
的基本配置,咱们明确指示 Spring
如何使用@Bean
注解建立每一个 bean,但在自动配置中,咱们已经经过@Component
和@Autowired
注解提供了足够的信息,说明如何建立所需的全部 bean。 惟一缺乏的信息是 Spring
应该在哪里寻找咱们的带有@Component
注解的 类,并把它注册为对应的bean。
@ Componentscan
注释包含一个参数 basePackages
,它容许咱们将包名称指定为一个 String
,Spring
将经过递归搜索来查找@Component
类。 在咱们的示例中,包是 com.milo.domain
,所以,咱们获得的配置类是:
@Configuration
@ComponentScan(basePackages = "com.milo.domain")
public class AutomatedAnnotationConfig {}
复制代码
ApplicationContext context =
new AnnotationConfigApplicationContext(AutomatedAnnotationConfig.class);
Car car = context.getBean(Car.class);
car.start();
复制代码
执行结果:
Started combustion engine
复制代码
经过和基于java
的基础配置比较,咱们发现基于 java
的自动配置方法有两个主要优势:
因此无特殊状况,自动配置是首选
除了构造函数注入,咱们还能够经过字段直接注入。 咱们能够将@Autowired
注解应用到所需的字段来实现这一点:
@Component
public class Car {
@Autowired
private Engine engine;
public void start() {
engine.turnOn();
}
}
复制代码
这种方法极大地减小了咱们的编码压力,可是它也有一个缺点,就是在使用字段以前,咱们将没法检查自动注入的对象是否为空。
构造函数注入的最后一种替代方法是 setter 注入,其中@Autowired
注解应用于与字段关联的 setter。 例如,咱们能够改变 Car
类,经过 setter 注入得到 Engine
对象,方法是用@Autowired
注解 setEngine
方法:
@Component
public class Car {
private Engine engine;
public void start() {
engine.turnOn();
}
public Engine getEngine() {
return engine;
}
@Autowired
public void setEngine(Engine engine) {
this.engine = engine;
}
}
复制代码
Setter 注入相似于字段注入,但它容许咱们与 注入对象交互。 在有些状况下,setter 注入可能特别有用,例如具备循环依赖关系,但 setter 注入多是三种注入技术中最不常见的,尽量优先使用构造函数注入。
另外一种配置方法是基于 xml
的配置。 咱们在 XML
配置文件中定义 bean
以及它们之间的关系,而后指示 Spring
在哪里找到咱们的配置文件。
第一步是定义 bean
。 咱们基本遵循与基于 java
的基本配置相同的步骤,但使用 xmlbean
元素代替。 在 XML
的状况下,咱们还必须显式地声明咱们打算使用 constructor-arg
元素注入到其余构造函数中的 bean
。 结合 bean
和 constructor-arg
元素,咱们获得如下 XML
配置:
<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="car" class="com.milo.domain.Car">
<constructor-arg ref="engine" />
</bean>
<bean id="engine" class="com.milo.CombustionEngine">
<constructor-arg ref="camshaft" />
<constructor-arg ref="crankshaft" />
</bean>
<bean id="camshaft" class="com.milo.Camshaft" />
<bean id="crankshaft" class="com.milo.Crankshaft" />
</beans>
复制代码
在 bean 元素中,咱们必须指定两个属性:
id
: bean 的惟一 ID (至关于带有@Bean
注解方法名)class
: 类的全路径(包括包名)对于 constructor-arg
元素,咱们只须要指定 ref
属性,它是对现有 bean ID
的引用。 例如,元素构造函数 <constructor-arg ref="engine" />
规定,具备 ID engine
(直接定义在 car
bean 之下)的 bean 应该被用做注入 car
bean 构造函数的 bean。
构造函数参数的顺序由 constructor-arg
元素的顺序决定。 例如,在定义 engine
bean 时,传递给 CombustionEngine
构造函数的第一个构造函数参数是 camshaft
bean,而第二个参数是 crankshaft
bean。
获取ApplicationContext
对象,咱们只需修改 ApplicationContext
实现类型。 由于咱们将 XML
配置文件放在类路径上,因此咱们使用 ClassPathXmlApplicationContext
:
ApplicationContext context =
new ClassPathXmlApplicationContext("basic-config.xml");
Car car = context.getBean(Car.class);
car.start();
复制代码
执行结果:
Started combustion engine
复制代码
如今,咱们已经摸清了Spring框架如何进行DI
,并正确地将全部依赖关系注入到咱们的应用程序中,可是咱们必须处理两个棘手的问题:
在基于 java
和基于 xml
的方法中,咱们已经指示 Spring
只使用 CombustionEngine
做为咱们的Engine
实现。 若是咱们将ElectricEngine
注册为符合 di
标准的部件会发生什么? 为了测试结果,咱们将修改基于 java
的自动配置示例,并用@Component
注解 ElectricEngine
类:
@Component
public class ElectricEngine implements Engine {
@Override
public void turnOn() {
System.out.println("Started electric engine");
}
}
复制代码
若是咱们从新运行基于 java 的自动配置应用程序,咱们会看到如下错误:
No qualifying bean of type 'com.dzone.albanoj2.spring.di.domain.Engine' available: expected single matching bean but found 2: combustionEngine,electricEngine
复制代码
因为咱们已经注释了用@Component
实现 Engine
接口的两个类ーー即 CombustionEngine
和ElectricEngine
ーー spring
如今没法肯定在实例化 Car
对象时应该使用这两个类中的哪个来知足 Engine
依赖性。 为了解决这个问题,咱们必须明确地指示 Spring
使用这两个 bean
中的哪个。
一种方法是给咱们的依赖对象命名,并在应用@Autowired
注解的地方使用@Qualifier
注解来肯定注入哪个依赖对象。 因此,@Qualifier
注解限定了自动注入的 bean,从而将知足需求的 bean 数量减小到一个。 例如,咱们能够命名咱们的CombustionEngine
依赖对象:
@Component("defaultEngine")
public class CombustionEngine implements Engine {
// ...代码省略,未改变
}
复制代码
而后咱们能够添加@Qualifier
注解,其名称和咱们想要注入的依赖对象的名称保持一致,这样,咱们Engine
对象在 Car
构造函数中被自动注入
@Component
public class Car {
@Autowired
public Car(@Qualifier("defaultEngine") Engine engine) {
this.engine = engine;
}
// ...existing implementation unchanged...
}
复制代码
若是咱们从新运行咱们的应用程序,咱们再也不报之前的错误:
Started combustion engine
复制代码
注意,若是没有显式申明bean名称的类都有一个默认名称,该默认名称就是类名首字母小写。 例如,咱们的 Combusttionengine
类的默认名称是 combusttionengine
若是咱们知道默认状况下咱们更喜欢一个实现,那么咱们能够放弃@Qualifier
注释,直接将@Primary
注释添加到类中。 例如,咱们能够将咱们的 Combusttionengine
、 ElectricEngine
和 Car
类更改成:
@Component
@Primary
public class CombustionEngine implements Engine {
// ...existing implementation unchanged...
}
@Component
public class ElectricEngine implements Engine {
// ...existing implementation unchanged...
}
@Component
public class Car {
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
// ...existing implementation unchanged...
}
复制代码
咱们从新运行咱们的应用程序,咱们会获得如下输出:
Started combustion engine
复制代码
这证实,虽然有两种可能性知足 Engine
依赖性,即 CombustionEngine
和 Electricengine
,但 Spring
可以根据@Primary
注释决定两种实现中哪种应该优先使用。
虽然咱们已经深刻讨论了 Spring DI
的基础知识,可是还有一个主要问题没有解决: 若是依赖关系树有一个循环引用会发生什么? 例如,假设咱们建立了一个 Foo
类,它的构造函数须要一个 Bar
对象,可是 Bar
构造函数须要一个 Foo
对象。
咱们可使用代码实现上面问题:
@Component
public class Foo {
private Bar bar;
@Autowired
public Foo(Bar bar) {
this.bar = bar;
}
}
@Component
public class Bar {
private Foo foo;
@Autowired
public Bar(Foo foo) {
this.foo = foo;
}
}
复制代码
而后咱们能够定义如下配置:
@Configuration
@ComponentScan(basePackageClasses = Foo.class)
public class Config {}
复制代码
最后,咱们能够建立咱们的 ApplicationContext
:
ApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
Foo foo = context.getBean(Foo.class);
复制代码
当咱们执行这个代码片断时,咱们看到如下错误:
Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bar': Requested bean is currently in creation: Is there an unresolvable circular reference?
复制代码
首先,Spring
尝试建立 Foo
对象。 在这个过程当中,Spring
认识到须要一个 Bar
对象。 为了构造 Bar
对象,须要一个 Foo
对象。 因为 Foo
对象目前正在构建中(这也是建立 Bar 对象的缘由) ,spring
认识到可能发生了循环引用。
这个问题最简单的解决方案之一是在一个类和注入点上使用@Lazy
注解。 这指示 Spring 推迟带注解的 bean 和带注释的@Autowired
位置的初始化。 这容许成功地初始化其中一个 bean,从而打破循环依赖链。 理解了这一点,咱们能够改变 Foo
和 Bar
类:
@Component
public class Foo {
private Bar bar;
@Autowired
public Foo(@Lazy Bar bar) {
this.bar = bar;
}
}
@Component
@Lazy
public class Bar {
@Autowired
public Bar(Foo foo) {}
}
复制代码
若是使用@Lazy
注解后从新运行应用程序,没有发现报告任何错误。
在本文中,咱们探讨了 Spring
的基础知识,包括 IoC
、 DI
和 Spring ApplicationContext
。 而后,咱们介绍了使用基于 java
的配置和基于 xml
的配置建立 Spring
应用程序的基本知识,同时研究了使用 Spring DI
时可能遇到的一些常见问题。 虽然这些概念一开始可能晦涩难懂,与 Spring
代码脱节,可是咱们能够从基底层认识Spirng
,但愿对你们有所帮助,谢谢你们。