Spring依赖注入源码学习:基于XML配置的DI源码解析
1.什么是依赖注入
控制反转(IoC)和依赖注入(DI)是Spring框架的核心设计思想,具体来说DI其实是对IoC的一种实现。抛开八股,依赖注入给使用者最直观的感受是Spring会自动给类(bean)的属性赋值,不需要我们手动地调用setter方法。所以,Spring不仅会自动地创建bean,并且还会给bean里面的属性赋值。给属性赋值的过程我们称之为依赖注入。
1.1 构造器注入和setter方法注入
我们先来看看在Java中如何给一个类的实例设置属性:
public class SimpleBased {private String property1;private int property2;public SimpleBased() {}public SimpleBased(String property1, int property2) {this.property1 = property1;this.property2 = property2;}public String getProperty1() {return property1;}public void setProperty1(String property1) {this.property1 = property1;}public int getProperty2() {return property2;}public void setProperty2(int property2) {this.property2 = property2;}@Overridepublic String toString() {return "SimpleBased{" +"property1='" + property1 + '\'' +", property2=" + property2 +'}';}
}
public class IOCTest {@Testpublic void simpleTest(){// 通过构造器注入SimpleBased simpleConstructorBased = new SimpleBased("property1", 2);System.out.println(simpleConstructorBased);// 通过setter注入SimpleBased simpleSetterBased = new SimpleBased();simpleSetterBased.setProperty1("property1.1");simpleSetterBased.setProperty2(3);System.out.println(simpleSetterBased);}
}
可以看到,我们设置一个对象的属性通过两种途径,一种是创建对象时通过构造方法指定属性值;另一种是在对象创建好后调用setter方法。
Spring框架的依赖注入(DI),本质上也是通过这两种方式实现,我们通常这两种方式为:构造器注入和setter方法注入。
我们以XML配置为例,来看看Spring是怎么实现这两种方式的属性注入:
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--构造器注入--><bean class="com.source.ioc.SimpleBased" name="simpleConstructorBased"><constructor-arg value="constructor" name="property1" type="java.lang.String"/><constructor-arg value="1" name="property2" type="int"/></bean><!--setter注入--><bean class="com.source.ioc.SimpleBased" name="simpleSetterBased"><property value="setter" name="property1"/><property value="1" name="property2"/></bean>
</beans>
@Test
public void simpleXmlTest(){ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml");SimpleBased simpleSetterBased = ac.getBean("simpleSetterBased", SimpleBased.class);System.out.println(simpleSetterBased);SimpleBased simpleConstructorBased = ac.getBean("simpleConstructorBased", SimpleBased.class);System.out.println(simpleConstructorBased);
}
运行结果如下:
在配置xml文件时,无论bean的属性是什么类型,标签中的value属性的类型均是String,Spring会自动帮我们做类型转换。
如果当前bean依赖其他bean时该怎么配置呢?
constructor-arg
标签和property
标签都支持ref属性,用于将bean的指定属性的值设置为对容器管理的另一个bean的引用。我们可以通过一个简单的例子来看看,首先我们定义好相关的bean:
public class SpringIOC {public static class SpringIOC1 {private String property;public SpringIOC1() {this.property = "i am SpringIOC1";}@Overridepublic String toString() {return "SpringIOC1{" +"property1='" + property + '\'' +'}';}}public static class SpringIOC2 {private String property;public SpringIOC2() {this.property = "i am SpringIOC2";}@Overridepublic String toString() {return "SpringIOC2{" +"property1='" + property + '\'' +'}';}}public static class SpringIOC3 {private String property;private SpringIOC1 springIOC1;private SpringIOC2 springIOC2;public SpringIOC3(SpringIOC1 springIOC1) {this.property = "i am SpringIOC3";this.springIOC1 = springIOC1;}public void setSpringIOC2(SpringIOC2 springIOC2) {this.springIOC2 = springIOC2;}@Overridepublic String toString() {return "SpringIOC3{" +"property='" + property + '\'' +", springIOC1=" + springIOC1 +", springIOC2=" + springIOC2 +'}';}}
}
接着我们在XML文件中配置这些bean:
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.source.ioc.SpringIOC.SpringIOC1" name="springIOC1"/><bean class="com.source.ioc.SpringIOC.SpringIOC2" name="springIOC2"/><bean id="springIOC3" class="com.source.ioc.SpringIOC.SpringIOC3"><constructor-arg name="springIOC1" ref="springIOC1"/><property name="springIOC2" ref="springIOC2"/></bean>
</beans>
最后编写测试方法:
@Test
public void simpleXmlBeanTest(){ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("ioc/spring-di-bean.xml");SpringIOC.SpringIOC3 springIOC3 = ac.getBean("springIOC3", SpringIOC.SpringIOC3.class);System.out.println(springIOC3);
}
控制台打印结果如下:
可以看到通过ref属性,成功将XML配置文件中的bean注入到目标bean当中。
1.2 自动注入
从上面的demo中,通过配置XML文件Spring能很好地帮助我们完成依赖注入。
但是这又产生了另一个问题,如果bean依赖很多其他bean,那么配置文件就会非常复杂,为此Spring提供了自动注入的功能。通过配置bean
标签的autowire属性来设置自动注入的模式,一共有四种模式:
1.no:默认值。不启用自动装配。bean必须引用由ref定义的元素。虽然配置要多一点,但是这样可以让系统结构和bean之间的关系更加清晰。2.byName:通过属性的名字的方式查找bean依赖的对象并为其注入。比如说某个bean的autowire属性为byName,并且有个属性名为a,有个setA方法,那么IoC容器会在配置文件中查找id/name的值包含a的bean,然后使用setter方法模式为其注入。注意这里的自动setter相比前面讲的手动setter注入有限制,即setXXX方法的XXX一定要对应一个容器中实例的name才行,否则不会注入。3.byType:通过属性的类型查找bean依赖的对象并为其注入。比如说某个bean的autowire属性为byType,并且有个属性名为a,有个setA方法,这个属性a的类型为com.A,那么IoC容器会在配置文件中查找class为com.A的bean,然后使用setter方法模式为其注入。注意这里的自动setter注入相比手动setter配注入有限制,即setXXX方法的XXX一定是属性名才行(第一个字母可以改变大小写)。如果容器有多个同类型的bean,那么抛出异常:expected single matching bean but found x: xxx。4.constructor:同byType一样,也是通过类型查找依赖对象。与byType的区别在于它不是使用setter方法注入,而是使用构造器注入。如果容器有多个同类型的bean,并且不能找到最合适的依赖,那么同样会抛出异常:expected single matching bean but found x: xxx。
如果使用自动注入模式,我们将SpringIOC3
去掉有参构造,增加无参构造:
public static class SpringIOC3 {private String property;private SpringIOC1 springIOC1;private SpringIOC2 springIOC2;public SpringIOC3() {}public void setSpringIOC2(SpringIOC2 springIOC2) {this.springIOC2 = springIOC2;}@Overridepublic String toString() {return "SpringIOC3{" +"property='" + property + '\'' +", springIOC1=" + springIOC1 +", springIOC2=" + springIOC2 +'}';}
}
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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.source.ioc.SpringIOC.SpringIOC1" name="springIOC1"/><bean class="com.source.ioc.SpringIOC.SpringIOC2" name="springIOC2"/><bean id="springIOC3" class="com.source.ioc.SpringIOC.SpringIOC3" autowire="byType"/></beans>
最后运行结果如下:
因为我们将autowire属性设置为byType,SpringIOC3
虽然有springIOC1和springIOC2两个属性,但是只有springIOC2有set方法,因此最后只有springIOC2自动注入成功。
[!IMPORTANT]
由于byType和byName均是通过set方法实现自动注入的,因此需要一个空参的构造方法来实例化bean。
这里顺带提一嘴,在最开始接触Spring框架时觉着自动注入功能很高级,如果从源码的角度来看,Spring在部分功能的实现确实挺复杂的,但是仅通过实现逻辑来看其实并没有那么复杂,我们简单来分析一下:
在前面的博客中我们有分析过,Spring会根据XML文件和注解来帮我们实例化bean,并将这些bean保存到Spring容器中。Spring 容器本质是一个大的Map,key为bean的name,value为bean的实例对象。
对于byType和byName,Spring只需要根据name或者类型去查这个大Map,取出bean的实例,通过反射调用setter方法完成依赖注入;
而对于constructor,则首先通过反射得到构造器的参数类型,然后根据类型查大Map取出bean实例,最后通过反射调用构造方法实例化bean并完成依赖注入。
上面只是简单的介绍整个自动注入的流程,Spring实际的实现肯定比这复杂得多。
2.基于XML配置的DI源码解析
想要了解依赖注入的源码实现,一定是绕不开Spring初始化bean的流程,这部分内容在之前的博客中已经详细讲述了,为保证前后文的连贯性,这里就简单过一下大致流程。
依赖注入的实现主要在于两点:保存XML的依赖配置信息、取出依赖配置信息完成依赖注入。
那么Spring会见XML的bean的依赖配置信息放在哪里呢?答:beanDefinition。我们先来看看Spring初始化bean的过程中,beanDefinition是怎么随着XML配置的变化而变化的。
我们先来看看方法入口:
2.1 加载beanDefinition
我们先来看看加载beanDefintion的粗略过程:
从两个角度来看看beanDefinition保存的信息:手动指定依赖注入信息和自动注入。
2.1.1 手动注入-setter
我们先来看看手动指定依赖注入信息:
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.source.ioc.SpringIOC.SpringIOC1" name="springIOC1"/><bean class="com.source.ioc.SpringIOC.SpringIOC2" name="springIOC2"/><bean id="springIOC3" class="com.source.ioc.SpringIOC.SpringIOC3"><property name="springIOC2" ref="springIOC2"/></bean>
</beans>
这时springIOC3的beanDefinition如下:
2.1.2 手动注入-构造器
我们再看看通过constructor-arg
标签通过构造器注入:
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.source.ioc.SpringIOC.SpringIOC1" name="springIOC1"/><bean class="com.source.ioc.SpringIOC.SpringIOC2" name="springIOC2"/><bean id="springIOC3" class="com.source.ioc.SpringIOC.SpringIOC3"><constructor-arg name="springIOC1" ref="springIOC1"/></bean>
</beans>
这时springIOC3的beanDefinition如下:
2.1.3 自动注入
我们把依赖注入改为自动注入:
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.source.ioc.SpringIOC.SpringIOC1" name="springIOC1"/><bean class="com.source.ioc.SpringIOC.SpringIOC2" name="springIOC2"/><bean id="springIOC3" class="com.source.ioc.SpringIOC.SpringIOC3" autowire="byName"/>
</beans>
继续打断点看看自动注入模式下的springIOC3的beanDefinition:
自动注入虽然有四种模式,但是反应在beanDefinition上面只会影响到autowireMode一个属性,autowireMode通过一个整型来区分不同的自动注入模式。
现在我们知道了,依赖注入的配置信息主要是通过beanDefinition的propertyValues属性、autowireMode属性和constructorArgumentValues属性完成的,这三个属性也是完成依赖注入的依据。
1.2节中我们提到过自动注入有四种模式,这四种模式和autowireMode的值对应如下:
no -> autowireMode=0
byName -> autowireMode=1
byType -> autowireMode=2
constructor -> autowireMode=3
由于自动注入没有明确指定需要注入哪些属性,Spring只能从设置的自动注入模式来推测,我们来看看四种自动注入模式下beanDefinition的propertyValues和constructorArgumentValues属性值:
public static class SpringIOC3 {private String property;private SpringIOC1 springIOC1;private SpringIOC2 springIOC2;public SpringIOC3() {}public SpringIOC3(SpringIOC1 springIOC1) {this.springIOC1 = springIOC1;}public void setSpringIOC2(SpringIOC2 springIOC2) {this.springIOC2 = springIOC2;}@Overridepublic String toString() {return "SpringIOC3{" +"property='" + property + '\'' +", springIOC1=" + springIOC1 +", springIOC2=" + springIOC2 +'}'; }}
2.2 依赖注入源码分析
前面我们讨论了Spring是如何保存XML中关于依赖注入的配置信息,接下来我们来看看Spring是如何完成依赖注入的。
我们先来分析一下依赖注入的时机,根据前文的介绍得知,依赖注入(无论是手动注入还是自动注入)只会通过两个途径:构造器注入或者setter方法注入。结合之前Spring初始化bean的博客来看,依赖注入会发生在bean的实例化阶段和属性填充阶段。
bean的实例化本质是通过得到Class对象,然后利用反射调用构造器完成实例化,因此当我们设置构造器注入时,会在这个阶段通过有参构造完成依赖注入。
属性填充则会调用对应字段的setter方法完成依赖注入。
前面的博客中我们已经详细分析了Spring初始化bean的全过程,这里就不再赘述,为了保证行文的流畅性,简单概括一下bean的初始化过程。
我们从refresh方法来看:
2.2.1 手动注入-构造器
我们重新调整一下Demo:
public class SpringIOC {public static class SpringIOC1 {private String property;public SpringIOC1() {this.property = "i am SpringIOC1";}@Overridepublic String toString() {return "SpringIOC1{" +"property1='" + property + '\'' +'}';}}public static class SpringIOC2 {private String property;public SpringIOC2() {this.property = "i am SpringIOC2";}@Overridepublic String toString() {return "SpringIOC2{" +"property1='" + property + '\'' +'}';}}public static class SpringIOC3 {private String property;private SpringIOC1 springIOC1;private SpringIOC2 springIOC2;public SpringIOC3() {}public SpringIOC3(SpringIOC1 springIOC1) {this.springIOC1 = springIOC1;}public void setSpringIOC2(SpringIOC2 springIOC2) {this.springIOC2 = springIOC2;}@Overridepublic String toString() {return "SpringIOC3{" +"property='" + property + '\'' +", springIOC1=" + springIOC1 +", springIOC2=" + springIOC2 +'}';}}
}
重新调整一下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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.source.ioc.SpringIOC.SpringIOC1" name="springIOC1"/><bean class="com.source.ioc.SpringIOC.SpringIOC2" name="springIOC2"/><bean id="springIOC3" class="com.source.ioc.SpringIOC.SpringIOC3"><constructor-arg name="springIOC1" ref="springIOC1"/><property name="springIOC2" ref="springIOC2"/></bean>
</beans>
可以看到,SpringIOC3
有两个构造器:空参构造和包含SpringIOC1
的构造,我们继续进到AbstractAutowireCapableBeanFactory
的createBeanInstance方法,关于这个方法的具体讲解在之前的博客(Spring源码学习(三):finishBeanFactoryInitialization-CSDN博客)中有提到,这里我们只聚焦于与依赖注入相关的关键部分:
// AbstractAutowireCapableBeanFactory.java的第1200行到1204行
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {return autowireConstructor(beanName, mbd, ctors, args);
}
determineConstructorsFromBeanPostProcessors方法是通过Spring后处理器来获得被@Autowire
注解修饰的构造器,这个方法只会在开启注解后才会生效,如果使用XML配置则该方法返回为null
。
在本小结给出的demo中,会进入if判断来调用autowireConstructor方法,我们来逐个分析一下这四个判断条件:
- ctors != null:false,因为没开启注解,determineConstructorsFromBeanPostProcessors返回为
null
; - mbd.getResolvedAutowireMode():false,因为没开启自动注入,beanDefinition的autowireMode的值为0,
AUTOWIRE_CONSTRUCTOR
的值为3; - mbd.hasConstructorArgumentValues():true,在2.1.2中有提到过,使用了
constructor-arg
注解后,beanDefinition的constructorArgumentValues属性不为空; - !ObjectUtils.isEmpty(args):false,这是判断实例化bean时是否使用外部参数,默认是不适用所以args是
null
。
if条件成立,接着我们进入到AbstractAutowireCapableBeanFactory
的autowireConstructor方法:
protected BeanWrapper autowireConstructor(String beanName, RootBeanDefinition mbd, @Nullable Constructor<?>[] ctors, @Nullable Object[] explicitArgs) {return new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs);
}
autowireConstructor方法里面会创建一个ConstructorResolver
的对象,然后再调用ConstructorResolver
的autowireConstructor方法,这里的this
其实就是beanFactory的本身,保存了所有bean的beanDefinition。
我们继续进到autowireConstructor的方法,注意这里入参ctors和explicitArgs均为null
,autowireConstructor方法较长,且在之前的博客中有详细讲述,我们这里只关注关键代码部分:
我们结合本小节的demo,看看Spring是怎么计算autowiring属性的值:
- chosenCtors != null:false,这里的chosenCtors 就是autowireConstructor的入参,这里的入参值为
null
; - mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR:false,不使用自动注入时beanDefinition的autowireMode的值为0,
AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR
的值为3。
[!NOTE]
这里的autowiring的其实是注解和xml配置两个方便综合考虑的结果。
再来看看resolveConstructorArguments方法,该方法做了两件事,计算构造函数最少参数数量属性minNrOfArgs和解析依赖的bean,在进到resolveConstructorArguments方法之前,先来回顾一下beanDefinition的constructorArgumentValues属性:
可以看到constructorArgumentValues由indexedArgumentValues和genericArgumentValues两个属性构成,当我们使用constructor-arg
标签时可以设置index属性,表示构造器的第几个入参,可以看个简单的例子:
<bean id="exampleBean" class="com.example.ExampleClass"><constructor-arg index="0" value="First Argument"/><constructor-arg index="1" value="Second Argument"/>
</bean>
Spring会将设置了index属性的constructor-arg
标签信息保存到indexedArgumentValues,而没设置index属性的constructor-arg
标签信息保存到genericArgumentValues属性当中。
在本小节的例子中,没有设置index属性,因此constructor-arg
标签信息会被保存到genericArgumentValues属性中。
在了解了constructorArgumentValues的基本构成之后,我们再来看看resolveConstructorArguments方法:
constructorArgumentValues由indexedArgumentValues和genericArgumentValues两个属性构成,minNrOfArgs则是这两个属性的size之和:
关于Spring将beanName解析为对应的bean的方法resolveValueIfNecessary,底层其实是通过beanFactory的getBean方法(套娃了属于是)完成的,这里就不再详细展开:
让我们重新回到autowireConstructor方法继续往下看:
Spring对候选构造器的排序规则如下:
- 访问权限修饰符越高的越靠前
- 参数越多的越靠前
排序完成后,会遍历这些候选构造器,选出最合适的一个构造器,这部分代码在之前的博客中已经讲解过了,这里就不再赘述,我们只看看minNrOfArgs的过滤作用:
在本小节的demo中,SpringIOC3虽然有空参构造,但是minNrOfArgs的值为1,0<1,空参构造就会被过滤掉。
在找到了合适的构造函数,已经入参对应的解析值后就可以根据反射来创建实例对象了,同时完成了依赖注入:
让我们来小结一下构造器依赖注入的整个流程,关键其实在于constructorArgumentValues属性:
- Spring读取XML文件,将
constructor-arg
标签的配置信息解析到beanDefinition的constructorArgumentValues属性中 - Spring会根据constructorArgumentValues计算构造器最少参数数量minNrOfArgs,根据minNrOfArgs来过滤构造器
- Spring会根据constructorArgumentValues将依赖关系解析成对应的bean(这里特指依赖其他bean的情况)
- 根据2和3的结果得到了构造器和构造器参数对应的值,通过反射创建对象,完成了依赖注入
2.2.2 手动注入-setter
我们继续沿用2.2.1的demo,Spring在实例化SpringIOC3
的对象时通过构造器注入成功注入了SpringIOC1
的依赖,接下来我们继续来看看SpringIOC2
是如何注入的。
前文有说过,setter注入是在bean的属性填充阶段完成的也就是populateBean方法,我们进入到populateBean方法来看看:
Spring到这一步是已经完成了SpringIOC3
的实例化,因此要想完成setter注入需要需要干两件事:将beanName解析为bean,调用对应属性的set方法将依赖注入到实例bean中。
我们进入到applyPropertyValues方法里面看看关键的部分:
我们继续进到setPropertyValues方法里面看看,这个方法位于AbstractPropertyAccessor
,
setPropertyValue方法调用链较多,我们来看看最后属性注入的代码:
2.2.3 自动注入
在1.2节中讲到了自动注入的四种方式,在2.1.3节中又讲到了四种自动注入模式在beanDefinition中对应的autowireMode的值,接下来我们来看看Spring是如何处理这四种模式的自动注入。
Demo:
public static class SpringIOC3 {private String property;private SpringIOC1 springIOC1;private SpringIOC2 springIOC2;public SpringIOC3() {}public SpringIOC3(SpringIOC1 springIOC1) {this.springIOC1 = springIOC1;}public void setSpringIOC2(SpringIOC2 springIOC2) {this.springIOC2 = springIOC2;}@Overridepublic String toString() {return "SpringIOC3{" +"property='" + property + '\'' +", springIOC1=" + springIOC1 +", springIOC2=" + springIOC2 +'}';}
}
2.2.3.1 autowire=“no”
创建bean实例的createBeanInstance方法:
可以看到最后使用的无参构造来实例化SpringIOC3,如果我们把Demo中的无参构造去掉会怎么样呢?程序会报错:
接着来看看属性填充部分的代码populateBean,可以看到核心逻辑一个没走,压根没触发属性填充:
2.2.3.2 autowire=“byName”
先来看看beanDefinition
再来看看实例化bean方法:
其实和 autowire=“no”
一样,最后也是采用无参构造实例化bean,也就是说使用byName自动注入,如果没有无参构造程序会报错。
这个其实也比较好理解,因为byName的本质是通过set方法完成依赖注入,而调用set方法的前提是能实例化bean,没有无参构造就无法实例化bean。
关键来看看属性填充方法:
继续进到autowireByName方法:
什么是符合要求的属性名称?,让我们进到unsatisfiedNonSimpleProperties方法来看看:
我们来逐行分析:
PropertyValues pvs = mbd.getPropertyValues();
因为我们没有主动在xml中配置property
标签,因此beanDefinition的propertyValues属性是个空数组,这里的pvs也是一个空数组。
PropertyDescriptor[] pds = bw.getPropertyDescriptors();
获得当前bean的属性描述符,Spring会通过反射解析bean的结构,并缓存符合要求的属性信息。简单来说,这一步可以获得有setter方法或者getter方法的属性信息。
pd.getWriteMethod() != null && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) && !BeanUtils.isSimpleProperty(pd.getPropertyType())
这一步是从pds中筛选出哪些属性要完成属性填充:
pd.getWriteMethod() != null:该属性有setter方法的
!isExcludedFromDependencyCheck(pd):该属性没有排除依赖检查之外,也就是不能为null
!pvs.contains(pd.getName()):xml中的property标签没有配置该属性
!BeanUtils.isSimpleProperty(pd.getPropertyType()):该属性不是简单属性
unsatisfiedNonSimpleProperties其实完成了一个属性检查的工作,它会筛选出类里面非简单类型的有setter方法的属性。
unsatisfiedNonSimpleProperties方法找到符合要求的属性名称后,通过getBean方法找到/创建bean作为该属性对应的属性值,最后完成属性注入。
[!IMPORTANT]
通过这里我们知道,byName并不会对所有属性进行依赖注入,它会有两步过滤,首先过滤出有setter/getter方法的属性,然后再过滤出里面有setter方法的非简单类型的属性。
2.2.3.3 autowire=“byType”
先来看看beanDefinition
对应的,进到autowireByType方法:
可以看到,byType和byName的大体流程一致,这里一个有意思的点是byType会对Object类型的属性进行过滤,而byName并没有。
这是因为byName通过beanName找到的bean是唯一的,而byType通过类型找到的bean可能不唯一,特别是对于Object类型,几乎可以接收所有类型的bean(子类的值可以赋值给父类)。
2.2.3.4 autowire=“constructor”
先来看看beanDefinition:
通过构造器注入,需要重点看看createBeanInstance方法:
可以看到,if判断为true,所以接下来的流程和2.2.1类似,这里就不再赘述,需要注意的一点是: