13.通过注解注入属性信息
🌶️ 13.通过注解注入属性信息
上一节中我们新增了一种bean的注册方式:包扫描+@Component
注解。这种方式实现了只用指定扫描哪些包,程序自动将这些包下使用了@Component
注解的bean注册到注册表中,同时bean还可以使用占位符的形式从属性文件中读取属性值来填充,但是这两部分是分开的,所以本节中在bean自动注册过程中引入了注解进行属性注入的模块,可以使用@Value
注解可以注入普通属性,使用@Autowired
和@Qualifier
注解注入bean对象,相关的代码我放到了
仓库
中,并且注解属性注入的过程和包扫描的过程都加入了bean的生命周期
原因
上节中实现了bean的自动扫描注册以及占位符形式注入bean的属性,但是这两部分是分开处理的,也就是说,bean的自动扫描注册的过程中没有引入任何属性,注册表存储的BeanDefinition
中的属性列表PropertyValues
部分没有在xml文件中配置的属性为空,所以本节中在bean的自动扫描注册的过程中引入了属性填充的功能,可以使用注解来进行属性填充,加入的模块关系如下图:
主要是在refresh
函数中进行属性占位符的替换,然后在空bean创建之后,属性填充之前将属性列表构造好直接填充,然后在属性填充时只需要填充xml文件中配置的bean属性
思路
为了在自动扫描注册之后引入注解属性填充的功能,肯定需要在bean的生命周期中进行改造,本节中为了引入注解属性填充的功能,需要在空bean创建之后,属性填充之前执行注解属性填充的功能,从而加载通过注解配置的属性来进行填充,之后再进行正常的xml配置文件中的属性填充。在xml文件读取的过程中,一旦配置了包扫描路径,此时就会执行bean的自动扫描注册,注册表中bean的BeanDefinition
中的PropertyValues
为空,之后再读取xml配置文件,如果此时还配置了bean,就会读取一份新的PropertyValues
,将其存入注册表中时会更新注册表,从而此时的bean的BeanDefinition
存储了两部分数据,第一部分是xml直接配置的属性,不为空,第二部分是使用注解配置的属性,为空。
下面的步骤就是将执行占位符替换,如果xml配置属性的过程中存在占位符,就在这一步进行替换,然后就是空bean的创建,下一步就是注解属性填充的部分,根据注解中配置的属性直接填充空bean中的属性,这一步补全了上面说的BeanDefinition
中的第二部分为空的属性,下来就是正常的属性填充,这一步利用上面所说的BeanDefinition
中的第一部分不为空的属性进行填充。经过这一步之后,bean的创建就完成了,之后完成一些剩下的步骤,就可以获取bean执行一些业务了。下面分步骤介绍注解属性填充的引入流程:
- 加载xml配置文件的时候,将包扫描路径下的所有bean加入注册表,同时将注解处理器
AutowiredAnnotationBeanPostProcessor
的bean也手动加入注册表 - 触发实例化前的修改逻辑,将上一步bean中的占位符替换成真正的值,同时保存一个字符串处理器便于后面使用
- 保存实例化后的修改逻辑,本节中的重点就是保存
AutowiredAnnotationBeanPostProcessor
的bean到容器中,便于后期触发 - 从
createBean
方法中的applyBeanPostProcessorsBeforeApplyingPropertyValues
为入口,里面触发AutowiredAnnotationBeanPostProcessor
的postProcessPropertyValues
方法,内部处理注解属性填充。针对Value中的占位符使用到了之前保存的字符串处理器进行属性填充,针对Autowired
和Qualifier
使用**getBean
**的方式进行属性填充 - 执行
createBean
之后的操作正常的从xml文件中读取到的正常属性填充,然后容器资源注入,初始化前操作,初始化,初始化后操作,保存销毁逻辑,返回bean
总结起来就是四步:
- 将注解处理器加入注册表中
- 将字符串处理器加入容器中
- 将注解处理器加入容器中
- 触发容器中的注解处理器的执行进行属性填充
具体的流程图如下图所示:
可以发现,在空bean创建之前都是一些准备工作,为了将注解属性填充引入现有的项目中,核心的步骤就是在createBean
中的空bean创建之后引入了新的一步applyBeanPostProcessorsBeforeApplyingPropertyValues
类的变化
下面介绍为了引入注解属性填充,项目中类的变化情况:
新增的类
StringValueResolver
:是一个待实现的接口,内部提供一个待实现的方法,可以根据传入的带占位符的字符串利用资源文件将占位符替换成真正的值,也就是上面提到的字符串处理器:Value
,Autowired
,Qualifier
:这是三个为了实现注解属性填充而引入的注解,Value
是为了进行普通属性填充,Autowired
和Qualifier
是配合起来进行bean的依赖注入AutowiredAnnotationBeanPostProcessor
:注解属性填充的核心类,实现了InstantiationAwareBeanPostProcessor
接口,根接口是BeanPostProcessor
,也就是说这是一个实例化后的修改逻辑,最终会从容器中获取到这个修改逻辑,执行其中的postProcessPropertyValues
方法,从而进行注解属性填充,而这个类的触发时机在createBean
中的空bean创建之后:
修改的类
PropertyPlaceholderConfigurer
:主要修改的地方是内部新增了一个字符串处理器类,字符串处理器内部的resolveStringValue
方法调用一个抽象出来的resolvePlaceholder
方法实现占位符的替换,这个字符串处理器不仅在xml属性占位符的替换时使用,还在Value注解占位符替换的时候使用。在执行完xml配置中的属性占位符的替换时,这个字符串处理器会被保存到beanFactory
中的一个容器中:ConfigurableBeanFactory
:新增两个待实现的方法,addEmbeddedValueResolver
方法是在xml属性占位符替换的时候调用,用来存储字符串处理器便于后面使用。resolveEmbeddedValue
方法是在注解属性填充中处理Value注解中的占位符时调用,内部调用所有保存的字符串处理器,尝试处理当前这个占位符:AbstractBeanFactory
:实现了ConfigurableBeanFactory
接口,从而实现了上面新增的两个方法,同时新增了一个容器用来保存所有的字符串处理器,便于后面直接调用这两个方法保存字符串处理器和调用字符串处理器:ClassPathBeanDefinitionScanner
:在包路径扫描完毕,bean自动注册之后,添加一步进行手动保存AutowiredAnnotationBeanPostProcessor
的bean注册信息,便于后面从容器中获取到AutowiredAnnotationBeanPostProcessor
这个实例化后的修改策略,从而进行注解属性填充:InstantiationAwareBeanPostProcessor
:新增一个待实现的方法postProcessPropertyValues
,在其中定义注解属性填充的逻辑,最终在AutowiredAnnotationBeanPostProcessor
类中实现:DefaultAdvisorAutoProxyCreator
:由于其实现了InstantiationAwareBeanPostProcessor
接口,而这个接口中新增了待实现的方法,所以这里简单实现了新增的postProcessPropertyValues
方法,内部没做任何实现,只是为了语法编译通过:AbstractAutowireCapableBeanFactory
:在createBean
方法中创建空bean之后引入一步applyBeanPostProcessorsBeforeApplyingPropertyValues
,主要是调用注解属性填充的逻辑,内部利用instanceof
找到实现了InstantiationAwareBeanPostProcessor
接口的AutowiredAnnotationBeanPostProcessor
类,调用其中的postProcessPropertyValues
方法,内部根据普通属性填充和bean属性填充分开操作,针对普通属性调用beanFactory
中的resolveEmbeddedValue
方法来操作,针对bean属性调用getBean
方法来操作:applyBeanPostProcessorsBeforeApplyingPropertyValues
方法的代码为:1 2 3 4 5 6 7 8 9 10 11 12 13 14
protected void applyBeanPostProcessorsBeforeApplyingPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) { for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor){ //根据配置的注解得到所有的属性注入值 //这里针对原有的属性列表没有做任何改变,原封不动的返回,如果此时 PropertyValues pvs = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessPropertyValues(beanDefinition.getPropertyValues(), bean, beanName); if (null != pvs) { for (PropertyValue propertyValue : pvs.getPropertyValues()) {//只要获取到的属性列表不是空,就新增一份 beanDefinition.getPropertyValues().addPropertyValue(propertyValue); } } } } }
BeanFactory
:内部新增一个只按照类型获取bean对象的方法,在DefaultListableBeanFactory
类中实现:DefaultListableBeanFactory
:对上面的只按照类型获取bean对象的方法进行实现,如果同一个类型只获取到一个就正常返回这个bean,如果获取到多个就不知道选哪一个,此时抛出异常:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public <T> T getBean(Class<T> requiredType) throws BeansException { List<String> beanNames = new ArrayList<>(); for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) { Class beanClass = entry.getValue().getBeanClass(); if (requiredType.isAssignableFrom(beanClass)) { beanNames.add(entry.getKey()); } } //如果一个类型下有一个对象才返回 if (1 == beanNames.size()) { return getBean(beanNames.get(0), requiredType); } //一个类型下有多个bean对象就抛出异常 throw new BeansException(requiredType + "expected single bean but found " + beanNames.size() + ": " + beanNames); }
根据上面的分析可以得出,最核心的代码就是在createBean
中引入了applyBeanPostProcessorsBeforeApplyingPropertyValues
方法从而在bean的生命周期中引入了注解属性填充的功能,剩下的一些类的变化都是为了辅助注解属性填充功能的加入
bean的创建和获取
根据上面的分析过程,下面使用debug来描述注解属性填充的流程,前提是本项目中有两个bean,其中userService
依赖于userDao
,依赖关系通过Autowired
注解的形式注入,并且userService
中还有一个使用Value
注入的普通属性,xml配置文件中只配置了包扫描路径:
加载配置文件,进入
doLoadBeanDefinitions
方法中读取xml配置文件中的内容调用
scanPackage
中的doScan
方法读取包扫描路径下的类包扫描完毕之后,将注解属性填充模块的核心类
AutowiredAnnotationBeanPostProcessor
手动存入注册表中,便于后期按照类型取出这个模块执行注解填充属性的功能:正常扫描xml文件中单独配置的bean,这里会扫描到字符串处理的逻辑,因为其在xml配置文件中正常配置,所以将其保存到注册表中:
invokeBeanFactoryPostProcessors
方法中触发实例化之前的修改逻辑,这里尝试替换bean属性中所有的占位符,针对每一个bean的每一个属性都尝试替换占位符:所有的属性替换完毕之后,保存一个字符串处理器,便于后面
@Value
注解执行属性注入的时候使用:保存实例化后的修改逻辑到容器中,这里主要是保存
AutowiredAnnotationBeanPostProcessor
核心类到容器中,然后在合适的时机触发,这里保存时就创建了这个bean:实例化所有的bean,在这里增加一步注解填充属性的步骤,以
userService
为例,一共经历下面几步:空bean的创建:
触发之前保存的按照注解填充属性:
调用之前保存的字符串处理器处理
@Value
注解的属性填充:调用
getBean
方法处理@Autowired
注解的属性填充:上面两种填充都是直接越过
PropertyValues
的构建直接填充属性
执行后面的步骤,得到最终的bean,执行相关的业务,最终的结果为:
总结
本节中引入了注解填充属性的功能,引入时机在空bean创建之后,正常方式属性填充之前。为了实现注解填充属性的功能,核心类是实现了InstantiationAwareBeanPostProcessor
接口的AutowiredAnnotationBeanPostProcessor
类,内部根据使用的注解进行属性填充,针对普通属性注入时使用的占位符设计了字符串处理器来替换,针对bean属性新增了一个只按照类型获取bean对象的方法,最终实现了注解填充属性的功能
核心就是先进行占位符替换(实例化前的修改逻辑),然后将占位符替换的方法进行保存,之后触发注解属性注入(实例化后的修改逻辑)时,还会再次用到占位符替换,这样的好处是直接使用xml配置属性占位符和注解配置的属性占位符都可以完成替换,在不影响原有项目的功能上做出了升级,而