文章摘要(AI生成)
在上一篇文章 spring如何实现控制反转?中,我们留下了一个拓展问题,即当有如下两个类:@Datapublic class A {private String c;private B b;}@Datapublic class B {private String s;public B(String
在上一篇文章 spring如何实现控制反转?中,我们留下了一个拓展问题,即当有如下两个类:
@Data
public class A {
private String c;
private B b;
}
@Data
public class B {
private String s;
public B(String s) {
this.s = s;
}
}
此时的对象A创建依赖对象B,那么我们如何使用我们的容器创建对象A和对象B呢?
提示
本文中为了更方便理解,我们把对象等同于bean,这样我们在对照spring源码的时候理解起来更顺畅,则之前的术语对应为:
名称 转换后 对象定义文档阅读器 bean定义文档阅读器 对象工厂 bean工厂 对象定义 bean定义 对象定义解析器 bean定义解析器
假如有个需求~~
在上述示例中,我们手动创建对象的过程可能如下:
public static void main(String[] args) {
B b = new B("hello");
A a = new A();
a.setB(b);
a.setC("123");
System.out.println(a);
}
//----------控制台输出----------
//A(c=123, b=B(s=hello))
当我们通过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/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="a" class="org.jdbc.demo.A">
<property name="b" ref="b"/>
<property name="c" value="123"/>
</bean>
<bean id="b" class="org.jdbc.demo.B">
<constructor-arg index="0" value="hello"/>
</bean>
</beans>
那么如果想要通过容器获取到对象a,整个的流程可能是这样的:
可以看到对比之前的过程中,我们的改动点有如下几个:
- bean定义中,新增属性集合和属性注入方式。
- 属性集合中的属性有两种取值方式,一种是简单赋值,直接设置这个属性对应简单对象的值;一种是依赖赋值,需要指出这个属性为哪个对象。
- 属性注入方式即告诉我们如果我们的属性为其他对象,则注入方式为通过名称注入还是通过类型注入。
- bean的创建
- 读取合并后的bean定义集合,获取合并后的bean定义配置
- 在创建对象后需要对对象进行属性注入
- 如果属性为依赖赋值,则需要通过bean工厂获取对应的对象,对应对象没有创建时,则要递归创建对象
- 如果属性为简单对象,则读取配置中的属性值进行注入
- 通过反射调用属性的设置方法进行属性赋值完成对象初始化
如何进行需求实现
我们先了解下这次迭代中引入的属性注入和依赖注入两个概念:
属性注入&依赖注入
属性注入和依赖注入都是指在代码编写时将对象所依赖的其他对象通过不同的方式注入到它们之中的技术。
属性注入是指在代码中明确声明某个对象所需要的属性,并在实例化时通过类的构造函数或设置函数(例如setter函数)将这些属性值传入到该对象中。它是一种静态注入的方式,因为属性值在实例化时就已经确定,不易变化。
依赖注入则是一种更为灵活的注入方式,它不仅可以注入对象的属性值,还可以注入对象本身。例如,在使用一个服务对象时,我们可以在构造函数或者setter函数中将该服务对象直接注入到另一个对象中。这种方式需要使用一个依赖注入框架来管理注入操作,以便自动创建和注入所需要的对象。依赖注入的好处是提高了代码的可读性和可维护性,同时降低了代码的耦合性,使得代码更加灵活。
针对上述新增的功能,我们实现的时候即是对bean工厂进行功能拓展。相比我们需求的1.0版本,在此版本中,我们bean的初始化过程也分为了两步:
- 通过反射构造函数实例化对象
- 通过反射设置函数进行属性赋值
所以我们只需要将上述的实现在bean工厂中进行实现
spring如何实现的?
spring中,bean工厂通过populateBean
方法实现了属性注入,主要分为三步:
- 当实例化的对象为空,则不进行属性注入
- 根据自动装配模式,对属性集合进行自动装配
- 通过反射将属性集合中的值注入到属性中
画作流程图,示意为:
对应的源码如下:
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
//当实例化的对象为空时直接返回
if (bw == null) {
if (mbd.hasPropertyValues()) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
}
else {
return;
}
}
//省略代码:为任何 InstantiationAwareBeanPostProcessors 提供在设置属性之前修改 bean 状态的机会。例如,这可以用于支持字段注入的样式。
PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
//省略代码:自动注入注解解析
boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
PropertyDescriptor[] filteredPds = null;
//省略InstantiationAwareBeanPostProcessors的属性设置过程
//依赖检查
if (needsDepCheck) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
checkDependencies(beanName, mbd, filteredPds, pvs);
}
if (pvs != null) {
//属性注入
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
我们点到属性注入的方法中,属性注入主要做了如下操作:
- 判断属性集合是否为空
- 遍历属性集合,对需要转换的属性值进行获取,包括属性类型为集合、内部对象、简单对象时的处理
- 获取到包含真实值的属性集合后,通过反射调用对象的设置方法完成对象赋值。
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
//属性集合判空
if (pvs.isEmpty()) {
return;
}
if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) {
((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());
}
MutablePropertyValues mpvs = null;
List<PropertyValue> original;
//获取属性集合
if (pvs instanceof MutablePropertyValues) {
mpvs = (MutablePropertyValues) pvs;
if (mpvs.isConverted()) {
// Shortcut: use the pre-converted values as-is.
try {
bw.setPropertyValues(mpvs);
return;
}
catch (BeansException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Error setting property values", ex);
}
}
original = mpvs.getPropertyValueList();
}
else {
original = Arrays.asList(pvs.getPropertyValues());
}
TypeConverter converter = getCustomTypeConverter();
if (converter == null) {
converter = bw;
}
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
List<PropertyValue> deepCopy = new ArrayList<>(original.size());
boolean resolveNecessary = false;
for (PropertyValue pv : original) {
//如果属性不需要转换,则直接添加对应的属性到结果属性集合中
if (pv.isConverted()) {
deepCopy.add(pv);
}
//如果属性需要转换
else {
String propertyName = pv.getName();
Object originalValue = pv.getValue();
//调用转换器,根据属性的类型获取到最终的属性值
Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
Object convertedValue = resolvedValue;
boolean convertible = bw.isWritableProperty(propertyName) &&
!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
if (convertible) {
convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
}
// 如果处理后的值和原值相等,则直接添加
if (resolvedValue == originalValue) {
if (convertible) {
pv.setConvertedValue(convertedValue);
}
deepCopy.add(pv);
}
else if (convertible && originalValue instanceof TypedStringValue &&
!((TypedStringValue) originalValue).isDynamic() &&
!(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
pv.setConvertedValue(convertedValue);
deepCopy.add(pv);
}
else {
resolveNecessary = true;
deepCopy.add(new PropertyValue(pv, convertedValue));
}
}
}
if (mpvs != null && !resolveNecessary) {
mpvs.setConverted();
}
//通过反射调用实例的属性设置方法为对象赋值
try {
bw.setPropertyValues(new MutablePropertyValues(deepCopy));
}
catch (BeansException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Error setting property values", ex);
}
}
拓展-通过注解进行依赖注入
在日常开发中,spring也提供了注解实现依赖注入的方式,即通过在属性上添加@Autowired
和@Resource
完成属性注入,那么可想而知,其实现也是在上述赋值方法populateBean
中完成的,即在已有流程中添加注解解析的判断,如果有相应注解,则通过不同的装配模式,将完成注入的bean提前设置到属性集合中:
而根据类型注入时,又要考虑注入的属性为集合时的情况:
@Autowired
private List<B> b;
所以我们需要通过依赖解析器根据我们属性的不同类型完成属性的依赖注入。
评论区