目录
- 前言
- 阅读准备
- 阅读指引
- 阅读建议
- 课程内容
- 一、依赖注入方式(前置知识)
- 1.1 手动注入
- 1.2 自动注入
- 1.2.1 XML的autowire自动注入
- 1.2.1.1 byType:按照类型进行注入
- 1.2.1.2 byName:按照名称进行注入
- 1.2.1.3 constructor:按照构造方法进行注入
- 1.2.1.4 其他
- 1.2.1.5 XML的autowire自动注入方式总结
- 1.2.2 @Autowired注解的自动注入
- 1.2.3 自动注入总结
- 二、依赖注入过程
- 2.1 简单回顾
- 2.2. 概念回顾
- 2.3 核心方法讲解
- 三、【寻找注入点】方法讲解
- 学习总结
前言
阅读准备
由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解的,所以不了解前置知识的情况下,大概率没办法看懂当前的内容。所以,特别推荐看看我前面的文章(自上而下次序):
- Spring底层核心原理解析【学习难度:★★☆☆☆】
- 手写简易Spring容器过程分析【学习难度:★★☆☆☆】
- Spring之底层架构核心概念解析【学习难度:★★★☆☆,重要程度:★★★★★】
- Bean的生命周期流程图【学习难度:☆☆☆☆☆,重要程度:★★★★★】
- Spring之Bean的生命周期源码解析——阶段一(扫描生成BeanDefinition)【学习难度:★★☆☆☆,重要程度:★★★☆☆】
- Spring之Bean的生命周期源码解析——阶段二(IOC之实例化)【学习难度:★★★★★,重要程度:★★★☆☆】
(PS:特别是《Bean的生命周期流程图》,帮大家【开天眼】,先了解下流程。毕竟【通过业务了解代码,远比通过代码了解业务简单的多】!!!!)
(PS:特别是《Bean的生命周期流程图》,帮大家【开天眼】,先了解下流程。毕竟【通过业务了解代码,远比通过代码了解业务简单的多】!!!!)
(PS:特别是《Bean的生命周期流程图》,帮大家【开天眼】,先了解下流程。毕竟【通过业务了解代码,远比通过代码了解业务简单的多】!!!!)
阅读指引
我们在上一节课已经说到过了,本次Spring源码剖析的总入口是new AnnotationConfigApplicationContext(“org.tuling.spring”);,这里就不再重复解释了。本节课要说的内容,是SpringIOC的属性填充/依赖注入,我们这里直接给到入口吧,调用链如下:(调用链比较深,不要纠结细枝末节)
- AbstractApplicationContext#refresh:刷新方法,不用在意
- AbstractApplicationContext#finishBeanFactoryInitialization:在这里实例化所有剩余的(非lazy-init)单例
- DefaultListableBeanFactory#preInstantiateSingletons:在这里实例化所有剩余的(非lazy-init)单例(上面的方法,核心干活的方法就是这里)
- DefaultListableBeanFactory#getBean:获取Bean的方法
- AbstractBeanFactory#doGetBean:返回指定bean的一个实例,它可以是共享的,也可以是独立的
- 上面这个
AbstractBeanFactory#doGetBean
里面的一段局部代码写的回调方法,如下:
// 如果是单例创建bean实例
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
- AbstractAutowireCapableBeanFactory#createBean:这个类的中心方法:创建一个bean实例,填充bean实例,应用后处理器,等等。
- AbstractAutowireCapableBeanFactory#populateBean:使用来自bean定义的属性值在给定的BeanWrapper中填充bean实例。
如上面的调用链所示,最后一个方法,才是我们本次要研究的核心方法。
阅读建议
- 看源码,切记纠结细枝末节,不然很容易陷进去。正常来说,看主要流程就好了
- 遇到不懂的,多看看类注释或者方法注释。Spring这种优秀源码,注释真的非常到位
- 如果你是idea用户,多用F11的书签功能。
- Ctrl + F11 选中文件 / 文件夹,使用助记符设定 / 取消书签 (必备)
- Shift + F11 弹出书签显示层 (必备)
- Ctrl +1,2,3…9 定位到对应数值的书签位置 (必备)
课程内容
一、依赖注入方式(前置知识)
在Spring中,属性注入的方式分为两种,分别是:【手动注入】和【自动注入】。
1.1 手动注入
在XML中定义Bean时,就是手动注入,因为是程序员手动给某个属性指定了值。如下:
<bean name="userService" class="com.luban.service.UserService">
<property name="orderService" ref="orderService"/>
</bean>
有经验的同学应该知道,上面这种底层是通过setXxx
方法进行注入的。另外,还有一种方式,是通过构造方法进行注入的,如下:
<bean name="userService" class="com.luban.service.UserService">
<constructor-arg index="0" ref="orderService"/>
</bean>
所以手动注入的底层也就是分为两种:【set方法注入】和【构造方法注入】。
1.2 自动注入
自动注入又分为两种:【XML的autowire自动注入】和【@Autowired注解的自动注入】。
1.2.1 XML的autowire自动注入
在XML中,我们可以在定义一个Bean时去指定这个Bean的自动注入模式,它有如下几种方式:
1.2.1.1 byType:按照类型进行注入
byType注入方式,底层是基于setXxx
方法实现的,所以setter方法不能少。这里说的类型是【入参】的类型。
Spring在通过byType的自动填充属性时流程是:
- 获取到set方法中的唯一参数的参数类型,并且根据该类型去容器中获取bean
- 如果找到多个,会报错
使用示例如下:
<bean id="userService" class="com.luban.service.UserService" autowire="byType"/>
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
如上示例的类型,就是指入参orderService
的类型OrderService
1.2.1.2 byName:按照名称进行注入
byType注入方式,底层是基于setXxx
方法实现的,所以setter方法不能少。这里说的【名称】,是指setXxx
后面的Xxx
部分。
所以,Spring在通过byName的自动填充属性时流程是:
- 找到所有set方法所对应的Xxx部分的名字
- 根据Xxx部分的名字去获取bean
使用示例如下:
<bean id="userXmlBean" class="org.tuling.spring.xml.bean.UserXmlBean" autowire="byName"/>
<bean id="walletXmlBean" class="org.tuling.spring.xml.bean.WalletXmlBean"/>
如上,我们定义了userXmlBean
的自动注入类型是byName
,并且定义了一个名字叫walletXmlBean
的bean。
public class UserXmlBean {
private WalletXmlBean wallet;
public void printProperty() {
System.out.println(wallet);
}
public void setWalletXmlBean(WalletXmlBean param) {
this.wallet = param;
}
}
如上,我们定义了一个UserXmlBean
,他有成员变量WalletXmlBean wallet
。同时给他声明了一个成员方法printProperty()
用来打印它的成员属性的地址。
测试代码:
public class MyXmlApplicationContextTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserXmlBean userXmlBean = (UserXmlBean)context.getBean("userXmlBean");
userXmlBean.printProperty();
}
// 系统输出:
// org.tuling.spring.xml.bean.WalletXmlBean@1d16f93d
}
如上,UserXmlBean
的WalletXmlBean
类型的属性出现了2个名称,一个是成员变量wallet
,另一个是setter方法入参中的param
,但是一点都不妨碍我们byName
注入。因为,根据byName
的规则,寻找的是setXxx
后面的Xxx
部分。还不够信服是吗?我们改一下UserXmlBean
里面的setter方法,如下:
public void setWalletXmlBean123(WalletXmlBean param) {
this.wallet = param;
}
这个时候再去调用,输出null
。
1.2.1.3 constructor:按照构造方法进行注入
constructor表示通过构造方法注入,其实这种情况就比较简单了,没有byType和byName那么复杂。
如果是constructor,那么就可以不写set方法了,当某个bean是通过构造方法来注入时,spring利用构造方法的参数信息从Spring容器中去找bean,找到bean之后作为参数传给构造方法,从而实例化得到一个bean对象,并完成属性赋值(属性赋值的代码得程序员来写)。
(PS:我们这里先不考虑一个类有多个构造方法的情况,后面单独讲推断构造方法。我们这里只考虑只有一个有参构造方法。)
其实构造方法注入相当于byType+byName。Spring在通过byName的自动填充属性时流程是:
- 通过构造方法中的参数类型去找bean,如果对应的类型只有一个bean,那就是它了;
- 如果找到多个会根据参数名确定
- 如果最后根据参数名都无法确定,则报错
使用示例如下:
<bean id="userXmlBean" class="org.tuling.spring.xml.bean.UserXmlBean" autowire="constructor"/>
<bean id="walletXmlBean123" class="org.tuling.spring.xml.bean.WalletXmlBean"/>
<bean id="walletXmlBean" class="org.tuling.spring.xml.bean.WalletXmlBean"/>
bean示例:
public class UserXmlBean {
private WalletXmlBean wallet;
public void printProperty() {
System.out.println(wallet);
}
public UserXmlBean(WalletXmlBean walletXmlBean) {
this.wallet = walletXmlBean;
}
}
具体的调用跟错误方式这边就不介绍了,大家回头自己试试吧
1.2.1.4 其他
其他,诸如:
- default:表示默认值,我们一直演示的某个bean的autowire,而也可以直接在<beans>标签中设置autowire,如果设置了,那么<bean>标签中设置的autowire如果为default,那么则会用<beans>标签中设置的autowire
- no:表示关闭autowire,不自动注入
1.2.1.5 XML的autowire自动注入方式总结
那么XML的自动注入底层其实也就是:
- set方法注入
- 构造方法注入
1.2.2 @Autowired注解的自动注入
@Autowired注解,本质上也是byType和byName的结合。它是先byType,如果找到多个则byName。这个跟xml构造方式注入原理如出一辙。就是:
- 先根据类型去找bean,如果对应的类型只有一个bean,那就是它了;
- 如果找到多个会根据属性名确定
- 如果最后根据属性名都无法确定,则报错
@Autowired注解可以写在:
- 属性上:先根据属性类型去找Bean,如果找到多个再根据属性名确定一个(属性注入)
- 构造方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个(构造方法注入)
- set方法上:先根据方法参数类型去找Bean,如果找到多个再根据参数名确定一个(set方法注入)
1.2.3 自动注入总结
可以发现XML中的自动注入是挺强大的,那么问题来了,为什么我们平时都是用的@Autowired注解呢?而没有用上文说的这种自动注入方式呢?
其实啊,@Autowired注解相当于XML中的autowire属性的注解方式的替代。从本质上讲,@Autowired注解提供了与autowire相同的功能,但是拥有更细粒度的控制和更广泛的适用性。
XML中的autowire控制的是整个bean的所有属性,而@Autowired注解是直接写在某个属性、某个set方法、某个构造方法上的。
再举个例子,如果一个类有多个构造方法,那么如果用XML的autowire=constructor,你无法控制到底用哪个构造方法,而你可以用@Autowired注解来直接指定你想用哪个构造方法。
同时,用@Autowired注解,还可以控制,哪些属性想被自动注入,哪些属性不想,这也是细粒度的控制。
二、依赖注入过程
2.1 简单回顾
依赖注入的过程,大体上其实能分为以下三步的:【寻找注入点】、【填充属性】、【填充属性后】。但其实,【寻找注入点】这个过程,会在两个地方被调用。第一个就是箭头所向,【实例化】阶段【BeanDefinition后置处理】那个地方。怎么理解呢?因为,【寻找注入点】的实现类就是【BeanDefinition后置处理】中的一个。
上面说的概念多少有点绕。简单来说,【寻找注入点】就是寻找被@Autowird
、@Value
、@Inject
、@Resource
注解修饰的属性、方法等等。
2.2. 概念回顾
在这个【实例化】过程中,涉及到了一些Spring底层设计的概念,我在上一个笔记里面有大概介绍过Spring底层概念的一些讲解,不记得的同学记得回去翻一翻。
主要涉及的概念有:
- BeanDefinition(设计图纸):BeanDefinition表示Bean定义,BeanDefinition中存在很多属性用来描述一个Bean的特征
- MergedBeanDefinitionPostProcessor:合并BeanDefinition后置处理器。但这里其实主要说的是
AutowiredAnnotationBeanPostProcessor
跟CommonAnnotationBeanPostProcessor
。他俩有什么作用呢?前者是处理Spring内部定义的@Autowired
跟@Value
自动注入注解;后者是处理jdk定义的@Resource
注解。既然都说到这了,那你们猜猜用到的是什么方法?嘿,不就是MergedBeanDefinitionPostProcessor
里面定义的方法嘛,赶紧去翻翻看。
CommonAnnotationBeanPostProcessor
接口定义如下:
/**
* 这个后置处理器通过继承InitDestroyAnnotationBeanPostProcessor和InstantiationAwareBeanPostProcessor注解,
* 获得了对@PostConstruct和@PreDestroy的支持。
* 另外,这个类的核心处理元素是@Resource注解
*/
public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
// 具体代码就不贴了。学到这里大家应该知道如何通过【接口】继承、实现来猜测类能力了吧
}
AutowiredAnnotationBeanPostProcessor
接口定义如下:
// 继承类跟CommonAnnotationBeanPostProcessor 如出一辙,唯一不同的是,继承了功能更强大的
// SmartInstantiationAwareBeanPostProcessor(InstantiationAwareBeanPostProcessor子类)
// 实现这个类,是为了实现里面的推断构造方法
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
2.3 核心方法讲解
本节【属性注入】,将会分两个部分来讲。第一部分是:【寻找注入点】;剩下的是第二部分。
先说第一部分,第一部分主要涉及【3个类,7个核心方法】。
第二部分,待定…