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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Spring IoC是如何使用BeanWrapper和Java内省结合起来给Bean属性赋值的【享学Spring】(下)

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


<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值的处理:


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


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


  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配置时代起到了非常大的作用,形如这样:


<bean class="foo.bar.xxx">
    <property name="referBeanName" ref="otherBeanName" />
</bean>


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


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


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


bw.setPropertyValues(new MutablePropertyValues(deepCopy))


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


总结


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


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


  1. 能答到populateBean()这里算是对这块知识入门了
  2. 能答到applyPropertyValues()这里,那基本对此回答就比较满意了
  3. 当然若能答到:通过自定义实现一个转换器+反射实现作为实现,而绕过Java内省机制。那势必就可以加分了~1. 若达到自定义、个性化定义BeanDefinition这块(虽然与本问题没有必然关联),也是可以加分的(毕竟是面试而非考试~)

相关文章
|
16天前
|
人工智能 Java API
Java也能快速搭建AI应用?一文带你玩转Spring AI可落地性
Java语言凭借其成熟的生态与解决方案,特别是通过 Spring AI 框架,正迅速成为 AI 应用开发的新选择。本文将探讨如何利用 Spring AI Alibaba 构建在线聊天 AI 应用,并实现对其性能的全面可观测性。
|
1月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
133 26
|
11天前
|
人工智能 Java API
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
|
2月前
|
存储 NoSQL Java
使用Java和Spring Data构建数据访问层
本文介绍了如何使用 Java 和 Spring Data 构建数据访问层的完整过程。通过创建实体类、存储库接口、服务类和控制器类,实现了对数据库的基本操作。这种方法不仅简化了数据访问层的开发,还提高了代码的可维护性和可读性。通过合理使用 Spring Data 提供的功能,可以大幅提升开发效率。
79 21
|
1月前
|
网络协议 Java Shell
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
java spring 项目若依框架启动失败,启动不了服务提示端口8080占用escription: Web server failed to start. Port 8080 was already in use. Action: Identify and stop the process that’s listening on port 8080 or configure this application to listen on another port-优雅草卓伊凡解决方案
70 7
|
7天前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
|
13天前
|
人工智能 Java API
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
|
19天前
|
Java 数据库 开发者
详细介绍SpringBoot启动流程及配置类解析原理
通过对 Spring Boot 启动流程及配置类解析原理的深入分析,我们可以看到 Spring Boot 在启动时的灵活性和可扩展性。理解这些机制不仅有助于开发者更好地使用 Spring Boot 进行应用开发,还能够在面对问题时,迅速定位和解决问题。希望本文能为您在 Spring Boot 开发过程中提供有效的指导和帮助。
69 12
|
22天前
|
Java 应用服务中间件 Maven
SpringBoot项目打包成war包
通过上述步骤,我们成功地将一个Spring Boot应用打包成WAR文件,并部署到外部的Tomcat服务器中。这种方式适用于需要与传统Servlet容器集成的场景。
41 8
|
2月前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
501 17
Spring Boot 两种部署到服务器的方式