上一篇文章
已经搞清楚了Spring是什么时候注册、创建、以及实例化bean的,那么对于Class A中存在属性 Class B,那么Class B是怎么注入到A中的呢?
public class UserServiceImpl {
private SchoolServiceImpl schoolService;
public String getSchoolServiceMethod(){
return schoolService.getTimeStr();
}
public void setSchoolService(SchoolServiceImpl schoolService) {
this.schoolService = schoolService;
}
}
第一种方式:
这里通过set方法将SchoolServiceImpl注入到UserServiceImpl中
,userService.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--set方法方式注入-->
<bean id="userService" class="com.learn.spring.spring01.service.UserServiceImpl">
<property name="schoolService" ref="schoolService"/>
</bean>
<bean id="schoolService" class="com.learn.spring.spring01.service.SchoolServiceImpl">
</bean>
</beans>
回顾上一篇内容,spring在启动的时候,会解析xml文件,并将xml文件的bean标签解析为BeanDefinition对象,debug发现,这一步还是和上一篇文章说的一样,正常注册。
接下里是创建bean,
上一篇第二步、第三步中有一个beanName 的list—-beanDefinitionNames,spring循环这个list,对于每一个元素,调用getBean(beanName)方法,创建bean、填充bean的属性、实例化bean
debug发现:
当userServiceImpl被创建完毕之后,会进入
populateBean这个方法,它负责填充bean属性
,userServiceImpl中的
属性就是schoolServiceImpl,
populateBean->applyPropertyValues->
valueResolver.resolveValueIfNecessary(这个方法比较关键,会根据入参类型的不同执行不同的策略)->resolveReference-
>this.beanFactory.getBean(resolvedName)最终还是会调用getBean(beanName)方法,剩下的就和上一篇文章分析的一样了,如果schoolServiceImpl存在,则返回;如果不存在,依旧创建、填充属性、实例化,
然后返回到resolveValueIfNecessary这里,把属性值设置进去
。然后接着实例化,那么userServiceImpl这个bean就处于就绪待使用状态了。
以上步骤相当于:
创建A->创建完毕->填充A属性,B->发现B缓存中没有对应的实例->创建B、填充B属性、实例化B->将实例化好的B设置到A中的属性B中->实例化A->完毕
思考:
有没有可能当userServiceImpl需要注入schoolServiceImpl的时候,schoolServiceImpl已经准备好的时候?
要搞清楚这个问题,就要知道几个关键点:
1、当getBean(beanName)时候,只要getSingleton(beanName)能获取到schoolServiceImpl,那么就不会创建,getSingleton里负责找bean的map是在getBean方法执行中,put进去的
2、也就是说,schoolServiceImpl要比userServiceImpl更早的调用getBean(beanName)方法,而调用顺序是beanDefinitionNames里的元素顺序决定的
3、beanDefinitionNames里的元素是什么时候放进去的?就是解析xml的时候,解析xml文件也是从上到下、循环解析。所以,
只要将schoolServiceImpl的bean标签放到userServiceImp之前就行了。
第二种方式:构造器方法
public class UserServiceImpl {
private SchoolServiceImpl schoolService;
public UserServiceImpl(SchoolServiceImpl schoolService) {
this.schoolService = schoolService;
}
public String getSchoolServiceMethod(){
return schoolService.getTimeStr();
}
}
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--将schoolService放到userService前面实例化,这样userService在填充属性的时候直接拿缓存里面的就行-->
<bean id="schoolService" class="com.learn.spring.spring01.service.SchoolServiceImpl">
</bean>
<bean id="userService" class="com.learn.spring.spring01.service.UserServiceImpl">
<!--构造器方式注入-->
<constructor-arg ref="schoolService"/>
</bean>
</beans>
构造器和set方法执行流程一点点不同,
当getBean(beanName)最终createBeanInstance的时候,此时会调用autowireConstructor方法,->resolveConstructorArguments->resolveValueIfNecessary,最终还是会调用这个方法
,由于创建实例的时候,调用构造函数,
this.SchoolServiceImpl = schoolServiceImpl 算是提前把属性填充好了
,所以再调用populateBean这个方法时,基本没什么可执行的代码,此时在这个方法里面是没做任何事情的。它不像set方法注入,最终会调用方法。它不执行任何方法
以上步骤相当于:
创建A->发现A具有构造器,使用代码中的构造器进行创建A->创建A的构造器参数是B,此时代码把B设置到A的属性B上->A创建完毕->填充属性->实例化->完毕
更多详细使用或者原理,可参考spring官方文档对于
依赖注入
的文档部分