依赖注入
依赖注入 (DI) 是一个过程,如果一个Bean中有其它bean作为其属性,容器会在创建 bean 时注入这些依赖项。
DI主要有两种方式:
- 基于构造函数注入
- 基于setter方法注入
我们用如下A、B两个类来讲解
A类定义如下
package com.yyoo.boot.bean;
public class A {
private String attr;
public A(){
System.out.println("A无参构造");
}
public A(String attr){
this.attr = attr;
System.out.println("A带参构造");
}
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
System.out.println("A的attr属性设置方法");
}
}
B类定义如下
package com.yyoo.boot.bean;
public class B {
private A a;
public B(){
System.out.println("B无参构造");
}
public B(A a){
this.a = a;
System.out.println("B带参构造");
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
System.out.println("B的setA方法");
}
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean id="a" class="com.yyoo.boot.bean.A">
<property name="attr" value="属性"></property>
</bean>
<bean id="b" class="com.yyoo.boot.bean.B">
<property name="a" ref="a"></property>
</bean>
<bean id="a1" class="com.yyoo.boot.bean.A">
<constructor-arg name="attr" value="构造注入属性"></constructor-arg>
</bean>
<bean id="b1" class="com.yyoo.boot.bean.B">
<constructor-arg name="a" ref="a1"></constructor-arg>
</bean>
</beans>
</beans>
编写Demo
package com.yyoo.boot.ioc;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo2 {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-ioc-1.xml");
}
}
执行结果
10:15:58.256 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2812cbfa
10:15:58.544 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 4 bean definitions from class path resource [spring-ioc-1.xml]
10:15:58.626 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a'
A无参构造
A的attr属性设置方法
10:15:58.718 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b'
B无参构造
B的setA方法
10:15:58.724 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a1'
A带参构造
10:15:58.731 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b2'
B带参构造
demo说明
A类中只有一个String类型的属性attr,B类依赖A类的实例。
xml配置中我们使用了setter和构造器两种方式配置
Demo中我们只定义了容器,没有执行getBean方法。
结果分析
我们没有执行getBean,这意味着,我们在初始化容器的时候,对应的Bean就开始实例化了,这是spring的预实例化机制。预实例化跟bean的作用域有关,后面会介绍。
容器初始化时,默认是根据xml配置的顺序来执行的。但其实有特殊情况。
将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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean id="b" class="com.yyoo.boot.bean.B">
<property name="a" ref="a"></property>
</bean>
<bean id="b1" class="com.yyoo.boot.bean.B">
<constructor-arg name="a" ref="a1"></constructor-arg>
</bean>
<bean id="a" class="com.yyoo.boot.bean.A">
<property name="attr" value="属性"></property>
</bean>
<bean id="a1" class="com.yyoo.boot.bean.A">
<constructor-arg name="attr" value="构造注入属性"></constructor-arg>
</bean>
</beans>
</beans>
再次执行,结果如下
10:54:49.994 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@59a6e353
10:54:50.289 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 4 bean definitions from class path resource [spring-ioc-1.xml]
10:54:50.383 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b'
B无参构造
10:54:50.411 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a'
A无参构造
A的attr属性设置方法
B的setA方法
10:54:50.478 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b2'
10:54:50.483 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a1'
A带参构造
B带参构造
结果分析
- Spring容器默认顺序加载xml中配置的bean
- 在执行setter方式的B类时,setter方式默认使用无参构造实例化,然后在注入属性a时,发现A类没有实例化,而且属性a对应的配置也是setter方式注入,所以调用a的无参构造,然后设置a的attr属性。a实例化之后才为B类注入a属性。
- 在执行构造器注入的B类时,发现需要构造器注入的A类做参数,于是先实例化A,然后实例化B。
以上结果的前提是A类没有其他依赖的bean,而B类依赖A类。但如果A和B相互依赖会怎么样?
循环依赖
我们将A类改造一下
package com.yyoo.boot.bean;
public class A {
private String attr;
private B b;
public A(){
System.out.println("A无参构造");
}
public A(String attr,B b){
this.attr = attr;
this.b = b;
System.out.println("A带参构造");
}
public String getAttr() {
return attr;
}
public void setAttr(String attr) {
this.attr = attr;
System.out.println("A的attr属性设置方法");
}
}
这样一来,A类依赖B类,B类依赖A类了
修改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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<beans>
<bean id="b" class="com.yyoo.boot.bean.B">
<property name="a" ref="a"></property>
</bean>
<bean id="b1" class="com.yyoo.boot.bean.B">
<constructor-arg name="a" ref="a1"></constructor-arg>
</bean>
<bean id="a" class="com.yyoo.boot.bean.A">
<property name="attr" value="属性"></property>
<property name="b" ref="b"></property>
</bean>
<bean id="a1" class="com.yyoo.boot.bean.A">
<constructor-arg name="attr" value="构造注入属性"></constructor-arg>
<constructor-arg name="b" ref="b1"></constructor-arg>
</bean>
</beans>
</beans>
再次执行demo结果如下
com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 com.yyoo.boot.ioc.Demo2,test
15:28:48.586 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2812cbfa
15:28:48.873 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 4 bean definitions from class path resource [spring-ioc-1.xml]
15:28:48.938 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b'
B无参构造
15:28:48.962 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a'
A无参构造
A的attr属性设置方法
A的setB方法
B的setA方法
15:28:49.017 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b1'
15:28:49.019 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'a1'
15:28:49.020 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'b1'
15:28:49.021 [main] WARN org.springframework.context.support.ClassPathXmlApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b1' defined in class path resource [spring-ioc-1.xml]: Cannot resolve reference to bean 'a1' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a1' defined in class path resource [spring-ioc-1.xml]: Cannot resolve reference to bean 'b1' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'b1': Requested bean is currently in creation: Is there an unresolvable circular reference?
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b1' defined in class path resource [spring-ioc-1.xml]: Cannot resolve reference to bean 'a1' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a1' defined in class path resource [spring-ioc-1.xml]: Cannot resolve reference to bean 'b1' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'b1': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:342)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:113)
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:707)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:198)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1354)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1204)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
at com.yyoo.boot.ioc.Demo2.test(Demo2.java:11)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a1' defined in class path resource [spring-ioc-1.xml]: Cannot resolve reference to bean 'b1' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'b1': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:342)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:113)
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:707)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:198)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1354)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1204)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330)
... 42 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'b1': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:330)
... 54 more
Process finished with exit code -1
结果分析
- setter方式的依赖注入,没有报错。其执行步骤如下为按xml的顺序,先实例化B,由于setter是使用的无参构造,在实例化时无需A类实例,然后在setter注入A类实例时,发现没有A类实例,实例化A类实例,然后注入attr和b(b已经实例化,只是没有注入a属性而已),然后B类注入a实例。
- 构造器注入,报错。这就算循环依赖,两个构造器都需要对方先实例化导致BeanCurrentlyInCreationException异常。
综上,我们在实际使用中尽量避免使用构造器注入的方式来必须循环依赖造成的问题。
上一篇:002-Spring Ioc入门
下一篇:004-Spring Ioc 依赖详解