前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的【享学Spring】

Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的【享学Spring】

作者头像
YourBatman
发布2019-09-03 16:07:06
1.1K0
发布2019-09-03 16:07:06
举报
文章被收录于专栏:BAT的乌托邦
前言

书写此篇博文的缘由是出自一道面试题:面试题目大概如标题所述。

我个人认为这道面试题问得是非常有水平的,因为它涉及到的知识点既有深度,又有广度,可谓一举两得~~~因此在这里分享给大家。

为了给此文做铺垫,前面已经有两篇文章分别叙述了Java内省和BeanWrapper,而且还分析了底层接口:属性访问器(PropertyAccessor)。若对此部分还不是很了解的话,建议可以先出门左拐或者单击【相关阅读】里的链接~

Spring IoC和Java内省的依赖关系说明

Spring需要依赖注入就需要使用BeanWrapper,上章节说了BeanWrapperImpl的实现大都委托给了CachedIntrospectionResults去完成,而CachedIntrospectionResults它的核心说法就是Java内省机制

从层层委托的依赖关系可以看出,Spring IoC的依赖注入(给属性赋值)是层层委托的最终给了Java内省机制,这是Spring框架设计精妙处之一。这也符合我上文所诉:BeanWrapper**这个接口并不建议应用自己去直接使用**~~~

那么本文就着眼于此,结合源码去分析Spring IoC容器它使用BeanWrapper完成属性赋值(依赖注入)之精华~

Spring IoC中使用BeanWrapper源码分析

Spring IoC我相信小伙伴并不陌生了,但IoC的细节不是本文的重点。为了便于分析,我把这个过程画一个时序图描述如下:

有了这个简略的时序图,接下来就一步一步的分析吧

doCreateBean() 创建Bean

任何创建Bean的过程,都得经历doCreateBean()。这句代码我们已经非常熟悉了,它在AbstractAutowireCapableBeanFactory里:

代码语言:javascript
复制
	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
		BeanWrapper instanceWrapper = null;
		...
		// 这一步简单的说:通过构造函数实例化Bean后,new BeanWrapperImpl(beanInstance)包装起来
		// 并且:initBeanWrapper(bw);  作用是注册ConversionService和registerCustomEditors() ...
		instanceWrapper = createBeanInstance(beanName, mbd, args);
		...
		// 给属性赋值:此处会实施BeanWrapper的真正实力~~~~
		// 注意:此处第三个参数传入的是BeanWrapper,而不是源生beanduixiang~~~ 
		populateBean(beanName, mbd, instanceWrapper);
		exposedObject = initializeBean(beanName, exposedObject, mbd);
		...
	}

doCreateBean这个方法完成整个Bean的实例化、初始化。而这里面我们最为关注的自然就是populateBean()这个方法,它的作用是完成给属性赋值,从时序图中也可以看出这是一个入口

populateBean():给Bean的属性赋值~
代码语言:javascript
复制
	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
		...
		// 从Bean定义里面把准备好的值都拿出来~~~
		// 它是个MutablePropertyValues,持有N多个属性的值~~~
		PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
		...
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			...
			// 此处会从后置处理,从里面把依赖的属性,值都拿到。比如大名鼎鼎的AutowiredAnnotationBeanPostProcessor就是在此处拿出值的~~~
			PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
			...
			pvs = pvsToUse;
		}
		...
		// 若存在属性pvs ,那就做赋值操作吧~~~(本处才是今天关心的重点~~~)
		if (pvs != null) {
			applyPropertyValues(beanName, mbd, bw, pvs);
		}
	}

深入到方法内部,它完成了k-v值的准备工作,很多重要的BeanPostProcessor也在此处得到执行。对于最终给属性赋值的步骤,是交给了本类的applyPropertyValues()方法去完成~~~

其实到了此处,理论上小伙伴就应该就能猜到接下来的核心下文了~

applyPropertyValues():完成属性赋值

这个方法的处理内容才是本文最应该关注的核心,它在处理数据解析、转换这一块还是存在不小的复杂度的~

代码语言:javascript
复制
	// 本方法传入了beanName和bean定义信息,以及它对应的BeanWrapper和value值们~
	protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
		if (pvs.isEmpty()) {
			return;
		}
		...
		MutablePropertyValues mpvs = null;
		List<PropertyValue> original;

		// 说明一下:为何这里还是要判断一下,虽然Spring对PropertyValues的内建实现只有MutablePropertyValues
		// 但是这个是调用者自己也可以实现逻辑的~~~so判断一下最佳~~~~
		if (pvs instanceof MutablePropertyValues) {
			mpvs = (MutablePropertyValues) pvs;
			// 此处有个短路处理:
			// 若该mpvs中的所有属性值都已经转换为对应的类型,则把mpvs设置到BeanWrapper中,返回
			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());
		}

		// 显然,若调用者没有自定义转换器,那就使用BeanWrapper本身~~~(因为BeanWrapper实现了TypeConverter 接口~~)
		TypeConverter converter = getCustomTypeConverter();
		if (converter == null) {
			converter = bw;
		}
		
		// 获取BeanDefinitionValueResolver,该Bean用于将bean定义对象中包含的值解析为应用于目标bean实例的实际值。
		BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);

		// Create a deep copy, resolving any references for values.
		// 此处翻译成深度拷贝不合适,倒不如翻译成深度解析更为合理~~~~
		List<PropertyValue> deepCopy = new ArrayList<>(original.size());
		boolean resolveNecessary = false;

		// 遍历没有被解析的original属性值们~~~~
		for (PropertyValue pv : original) {
			if (pv.isConverted()) {
				deepCopy.add(pv);
			} else { // 那种还没被解析过的PropertyValue此处会一步步解析~~~~
				String propertyName = pv.getName(); // 属性名称
				Object originalValue = pv.getValue(); // 未经类型转换的值(注意:是未经转换的,可能还只是个字符串或者表达式而已~~~~)

				// 最为复杂的解析逻辑~~~
				Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
				Object convertedValue = resolvedValue;

				// 属性可写 并且 不是嵌套(如foo.bar,java中用getFoo().getBar()表示)或者索引(如person.addresses[0])属性
				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));
				}
			}
		}

		// 标记mpvs已经转换
		if (mpvs != null && !resolveNecessary) {
			mpvs.setConverted();
		}

		// Set our (possibly massaged) deep copy.
		// 使用转换后的值进行填充~~~~~~~~~~
		try {
			bw.setPropertyValues(new MutablePropertyValues(deepCopy));
		} catch (BeansException ex) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Error setting property values", ex);
		}

	}

	// 属性值的转换
	@Nullable
	private Object convertForProperty(@Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) {
		// 需要特别注意的是:convertForProperty方法是BeanWrapperImpl的实例方法,并非接口方法
		// 这个方法内部就用到了CachedIntrospectionResults,从何就和Java内省搭上了关系~~~
		if (converter instanceof BeanWrapperImpl) {
			return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName);
		} else { // 自定义转换器
			PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
			MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
			return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam);
		}
	}

说明:BeanDefinitionValueResolver是Spring一个内建的非public类,它在上述步骤中承担了非常多的任务,具体可参考此处:BeanDefinitionValueResolver和PropertyValues


关于BeanDefinitionValueResolver此处我补充一点:关于占位符的使用,形如我们可以这样配置:

代码语言:javascript
复制
<bean id="myPerson" class="com.fsx.bean.Person">
    <property name="name" value="${diy.name}"/>
    <property name="age" value="18"/>
</bean>

此处我想补充的是对${diy.name}这个占位符的解析时机:它并不发生在BeanDefinitionValueResolver里。

我们知道它的resolveValueIfNecessary方法能解析各式各样的值,此处就补充说明它对string值的处理:

代码语言:javascript
复制
// @since 1.2
class BeanDefinitionValueResolver {
	@Nullable
	public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
		...
		// 处理String类型的值:处理SpEL表达式,但是它并不处理占位符~~~
		else if (value instanceof TypedStringValue) {
			TypedStringValue typedStringValue = (TypedStringValue) value;
			// 此处evaluate最终调用的是我们熟悉的this.beanFactory.evaluateBeanDefinitionString(value, this.beanDefinition)方法
			// 而它底层调用是this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope));
			Object valueObject = evaluate(typedStringValue);
		}
	}
}

由此可见此类最终做的是this.beanExpressionResolver.evaluate()这个计算处理,它处理的是SpEL表达式,此处并未处理占位符。

但是呢,我们知道Spring这样配置是可行的,所以我们有理由去相信:处理占位符操作发生在这个步骤之前(这个步骤是什么?答:**给属性赋值阶段,已经属于容器初始化非常靠后的阶段了~**)

至于此部分的占位符在哪解析的,其实之前是分析过步骤的,那么此处我也只给出步骤提示吧:

  1. Spring处理属性占位符依赖于PropertyResourceConfigurer来处理的~
  2. PropertyResourceConfigurer是个BeanFactoryPostProcessor,它会在Bean工厂完成后,借助BeanDefinitionVisitor对所有的BeanDefinition进行visitBeanDefinition()处理(这里会调用valueResolver.resolveStringValue(strVal)进行占位符的处理~~)
  3. 包括xml配置的ParentName、BeanClassName、FactoryBeanName、FactoryMethodName、Scope、构造函数类型。。。等等都是能够通过属性文件来配置的
  4. 大名鼎鼎的实现类:PropertyPlaceholderConfigurerPropertySourcesPlaceholderConfigurer大家应该非常熟悉~
  5. 言外之意:若你没向容器内注入**PropertyResourceConfigurer**这个Bean,xml里是使用占位符是不生效的哟~~~(SpringBoot默认给配置的是PropertySourcesPlaceholderConfigurer

处理xml中Bean定义的占位符的核心方法是这:

代码语言:javascript
复制
	public void visitBeanDefinition(BeanDefinition beanDefinition) {
		visitParentName(beanDefinition);
		visitBeanClassName(beanDefinition);
		visitFactoryBeanName(beanDefinition);
		visitFactoryMethodName(beanDefinition);
		visitScope(beanDefinition);
		// 这里 它会处理所有的配置的Bean定义的占位符、数组、List等等
		if (beanDefinition.hasPropertyValues()) {
			visitPropertyValues(beanDefinition.getPropertyValues());
		}
		if (beanDefinition.hasConstructorArgumentValues()) {
			ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
			visitIndexedArgumentValues(cas.getIndexedArgumentValues());
			visitGenericArgumentValues(cas.getGenericArgumentValues());
		}
	}

	// 显然,重点在resolveValue这个方法~~~
	protected void visitPropertyValues(MutablePropertyValues pvs) {
		PropertyValue[] pvArray = pvs.getPropertyValues();
		for (PropertyValue pv : pvArray) {
			Object newVal = resolveValue(pv.getValue());
			if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
				pvs.add(pv.getName(), newVal);
			}
		}
	}

	@Nullable
	protected Object resolveValue(@Nullable Object value) {
		if (value instanceof BeanDefinition) {
			visitBeanDefinition((BeanDefinition) value);
		}
		...
		else if (value instanceof TypedStringValue) {
			...
			// 处理占位符~~~
			String visitedString = resolveStringValue(stringValue);
			...
		}
	}

说明:PropertyResourceConfigurer不仅能够解析处理Bean定义的占位符,还能够loadProperties(result);加载属性文件的~ 具体可参考:【小家Spring】详解PropertyPlaceholderConfigurer、PropertyOverrideConfigurer等对属性配置文件Properties的加载和使用


从命名中就能看出,它处理BeanDefinition的各式各样的情况,它主要是在xml配置时代起到了非常大的作用,形如这样:

代码语言:javascript
复制
<bean class="foo.bar.xxx">
    <property name="referBeanName" ref="otherBeanName" />
</bean>

因为我们知道在xml时代配置Bean非常的灵活:引用Bean、Map、List甚至支持SpEL等等,这一切权得益于BeanDefinitionValueResolver这个类来处理各种case~

其实在现在注解大行其道的今天,配置Bean我们大都使用@Bean来配置,它是一种工厂方法的实现,因此这个处理类的作用就被弱化了很多。但是,但是,但是,它仍旧是我们实施定制化BeanDefinition的一个有力武器~

applyPropertyValues()这一步完成之后,就彻底完成了对Bean实例属性的赋值。从中可以看到最终的赋值操作,核心依赖的就是这么一句话:

代码语言:javascript
复制
bw.setPropertyValues(new MutablePropertyValues(deepCopy))

并且从转换的逻辑我们也需要知道的是:IoC并不是100%得使用BeanWrapper的,若我们是自定义了一个转换器,其实是可以不经过Java内省机制,而是直接通过反射来实现的,当然并不建议这么去做~

总结

BeanWrapper体系相比于 Spring 中其他体系是比较简单的,它作为BeanDefinitionBean转换过程中的中间产物,承载了 bean 实例的包装、类型转换、属性的设置以及访问等重要作用(请不要落了访问这个重要能力)。

关于此面试题怎么去回答,如果是我主考我会这么评价回答:

  1. 能答到populateBean()这里算是对这块知识入门了
  2. 能答到applyPropertyValues()这里,那基本对此回答就比较满意了
  3. 当然若能答到:通过自定义实现一个转换器+反射实现作为实现,而绕过Java内省机制。那势必就可以加分了~
  4. 若达到自定义、个性化定义BeanDefinition这块(虽然与本问题没有必然关联),也是可以加分的(毕竟是面试而非考试~)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年07月15日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Spring IoC和Java内省的依赖关系说明
  • Spring IoC中使用BeanWrapper源码分析
    • doCreateBean() 创建Bean
      • populateBean():给Bean的属性赋值~
        • applyPropertyValues():完成属性赋值
          • 总结
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档