ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Spring 自定义属性解析器源码解析:PropertyEditor 注册与注入全链路(source-code-hunter)

Spring 自定义属性解析器源码解析:PropertyEditor 注册与注入全链路(source-code-hunter) Spring 自定义属性解析器源码解析PropertyEditor 注册与注入全链路source-code-hunter【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter本文以 source-code-hunter 仓库中的 Spring-Custom-attribute-resolver.md 为核心结合仓库内其他 IoC 源码笔记从「自定义属性编辑器PropertyEditor」的使用案例出发深入剖析 Spring 容器如何完成自定义属性编辑器的注册、配置与最终的类型转换帮助读者真正理解 Spring IoC 中字符串属性值到目标类型对象之间的转换机制。一、为什么需要自定义属性解析器在 Spring 的 XML 配置中property namexxx value...注入的永远是一个字符串。当目标属性是java.util.Date、自定义实体对象如Address等非字符串类型时Spring 必须借助属性编辑器PropertyEditor将字符串转换为目标类型。Java 原生提供了java.beans.PropertyEditor接口Spring 在其基础上扩展出PropertyEditorRegistrar、PropertyEditorRegistry等组件并提供了CustomEditorConfigurer这个BeanFactoryPostProcessor作为统一的注册入口。整个链路可以概括为实现PropertyEditorSupport子类定义字符串 → 目标类型的转换规则通过PropertyEditorRegistrar将编辑器注册到PropertyEditorRegistry在 XML 中配置CustomEditorConfigurer把注册器或customEditors映射注入进去Bean 实例化填充属性populateBean→applyPropertyValues时Spring 找到对应编辑器完成转换。仓库中 BeanFactoryPostProcessor.md 也给出了同一机制的另一个完整可运行示例String → Address 类型转换可相互印证。二、完整用例把字符串 2020-01-01 01:01:01 注入到 Date 属性2.1 XML 配置文件?xml version1.0 encodingUTF-8? beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlnshttp://www.springframework.org/schema/beans xsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd bean classorg.springframework.beans.factory.config.CustomEditorConfigurer property namepropertyEditorRegistrars list bean classcom.huifer.source.spring.bean.DatePropertyRegister/ /list /property property namecustomEditors map entry keyjava.util.Date valuecom.huifer.source.spring.bean.DatePropertyEditor /entry /map /property /bean bean idapple classcom.huifer.source.spring.bean.Apple property namedate value2020-01-01 01:01:01/ /bean /beans配置要点propertyEditorRegistrars注入一个PropertyEditorRegistrar实现列表由注册器负责把编辑器绑定到具体类型customEditors以 Map 形式直接声明「目标类型 → 编辑器类名」的映射key 为目标类型全限定名value 为编辑器类全限定名目标 beanapple的date属性接收字符串2020-01-01 01:01:01需要被转换为Date才能注入成功。2.2 方式一实现 PropertyEditorRegistrar 注册器public class DatePropertyRegister implements PropertyEditorRegistrar { Override public void registerCustomEditors(PropertyEditorRegistry registry) { registry.registerCustomEditor(Date.class, new CustomDateEditor( new SimpleDateFormat(yyyy-MM-dd), true) ); } }CustomDateEditor是 Spring 自带的标准日期编辑器true表示允许空字符串。这里通过registry.registerCustomEditor(Date.class, ...)把Date类型绑定到自定义的日期编辑器上。2.3 方式二继承 PropertyEditorSupport 自定义转换规则public class DatePropertyEditor extends PropertyEditorSupport { private String format yyyy-MM-dd; public String getFormat() { return format; } public void setFormat(String format) { this.format format; } Override public void setAsText(String text) throws IllegalArgumentException { System.out.println(text); SimpleDateFormat sdf new SimpleDateFormat(format); try { Date date sdf.parse(text); this.setValue(date); } catch (Exception e) { e.printStackTrace(); } } }要点PropertyEditorSupport是java.beans.PropertyEditor的默认适配实现核心方法setAsText(String)接收配置中的字符串解析成目标对象后调用setValue(...)暂存Spring 最终通过editor.getValue()取回转换结果。三、PropertyEditorRegistrar 注册流程解析在DatePropertyRegister.registerCustomEditors方法上打断点可以看到完整的调用堆栈图片 1其调用层次揭示了注册发生的阶段容器创建 Bean 期间doCreateBean→instantiateBean等触发注册逻辑最终进入自定义注册器。3.1 registerCustomEditor 的两个重载断点进入后最先经过PropertyEditorRegistry接口的第一个重载方法它把参数转交给带propertyPath的重载Override public void registerCustomEditor(Class? requiredType, PropertyEditor propertyEditor) { registerCustomEditor(requiredType, null, propertyEditor); }第二个重载是实际执行的实现来自PropertyEditorRegistrySupportOverride public void registerCustomEditor(Nullable Class? requiredType, Nullable String propertyPath, PropertyEditor propertyEditor) { if (requiredType null propertyPath null) { throw new IllegalArgumentException(Either requiredType or propertyPath is required); } if (propertyPath ! null) { if (this.customEditorsForPath null) { this.customEditorsForPath new LinkedHashMap(16); } this.customEditorsForPath.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType)); } else { if (this.customEditors null) { this.customEditors new LinkedHashMap(16); } // 放入 customEditors map对象中 this.customEditors.put(requiredType, propertyEditor); this.customEditorCache null; } }从这个实现可以提炼出两个关键数据结构customEditorsForPath按属性路径如apple.date维度注册编辑器适用于只针对某个具体属性生效的场景customEditors按目标类型如java.util.Date维度注册编辑器对全局所有该类型的属性生效——本用例走的是这一分支。3.2 registry 对象从哪来AbstractBeanFactory#registerCustomEditors调试时展开registry参数其运行时类型是PropertyEditorRegistrySupport图片 2 展示了 IDE 中查看this.customEditors变量的结果LinkedHashMap中java.util.Date映射到CustomDateEditor其内部dateFormat的 pattern 为yyyy-MM-dd。这个registry对象由org.springframework.beans.factory.support.AbstractBeanFactory#registerCustomEditors传入该方法会在 Bean 工厂初始化、需要类型转换时被调用protected void registerCustomEditors(PropertyEditorRegistry registry) { PropertyEditorRegistrySupport registrySupport (registry instanceof PropertyEditorRegistrySupport ? (PropertyEditorRegistrySupport) registry : null); if (registrySupport ! null) { registrySupport.useConfigValueEditors(); } if (!this.propertyEditorRegistrars.isEmpty()) { for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) { try { /** * {link ResourceEditorRegistrar#registerCustomEditors(org.springframework.beans.PropertyEditorRegistry)}或者 * {link PropertyEditorRegistrar#registerCustomEditors(org.springframework.beans.PropertyEditorRegistry)} */ registrar.registerCustomEditors(registry); } catch (BeanCreationException ex) { Throwable rootCause ex.getMostSpecificCause(); if (rootCause instanceof BeanCurrentlyInCreationException) { BeanCreationException bce (BeanCreationException) rootCause; String bceBeanName bce.getBeanName(); if (bceBeanName ! null isCurrentlyInCreation(bceBeanName)) { if (logger.isDebugEnabled()) { logger.debug(PropertyEditorRegistrar [ registrar.getClass().getName() ] failed because it tried to obtain currently created bean ex.getBeanName() : ex.getMessage()); } onSuppressedException(ex); continue; } } throw ex; } } } if (!this.customEditors.isEmpty()) { this.customEditors.forEach((requiredType, editorClass) - registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass))); } }流程解读若registry是PropertyEditorRegistrySupport先调用useConfigValueEditors()启用默认的配置值编辑器如字符串转数组、集合、Class 等遍历propertyEditorRegistrars列表逐个调用registrar.registerCustomEditors(registry)——本用例中DatePropertyRegister正好实现了接口void registerCustomEditors(PropertyEditorRegistry registry);因此会在这里被回调如果注册器抛出的BeanCreationException根因是BeanCurrentlyInCreationException即注册器内部尝试获取正在创建中的 Bean常见于循环依赖场景会记录日志并跳过该注册器继续执行最后遍历customEditors映射通过BeanUtils.instantiateClass(editorClass)实例化每个编辑器类并注册到registry。propertyEditorRegistrars与customEditors正是定义在AbstractBeanFactory中的成员变量也是 XML 中CustomEditorConfigurer两个property注入的目标。3.3 为什么最终拿到的是 DatePropertyEditor顺着疑问「为什么注册的结果是com.huifer.source.spring.bean.DatePropertyEditor」回到配置文件property namecustomEditors map entry keyjava.util.Date valuecom.huifer.source.spring.bean.DatePropertyEditor /entry /map /property对应的 setter 方法位于CustomEditorConfigurerpublic void setCustomEditors(MapClass?, Class? extends PropertyEditor customEditors) { this.customEditors customEditors; }也就是说XML 中customEditors的entry keyjava.util.Date value...被装配成一个MapClass?, Class? extends PropertyEditor注入到CustomEditorConfigurer随后由AbstractBeanFactory#registerCustomEditors中的this.customEditors.forEach((requiredType, editorClass) - registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)))完成「类型 → 编辑器实例」的最终注册。这里BeanUtils.instantiateClass会通过反射调用无参构造器创建DatePropertyEditor实例。补充说明CustomEditorConfigurer本质上是BeanFactoryPostProcessor的一个实现其postProcessBeanFactory会把propertyEditorRegistrars通过beanFactory.addPropertyEditorRegistrar(...)加入 Bean 工厂、把customEditors逐个registerCustomEditor注册进去。仓库文档 BeanFactoryPostProcessor.md 中以Address对象为例给出了完整可运行示例AddressParse extends PropertyEditorSupport解析四川,成都为Address(province四川, city成都)并指出注册器真正被用到是在 Bean 填充属性阶段。四、applyPropertyValues属性注入时的类型转换编辑器注册完成后真正触发转换发生在 Bean 创建流程的populateBean→applyPropertyValues。该方法定义于AbstractAutowireCapableBeanFactoryprotected 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; // 没有解析的属性 ListPropertyValue original; if (pvs instanceof MutablePropertyValues) { mpvs (MutablePropertyValues) pvs; if (mpvs.isConverted()) { //MutablePropertyValues 对象中存在转换后对象直接赋值 // 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 BeanDefinitionValueResolver valueResolver new BeanDefinitionValueResolver(this, beanName, mbd, converter); // Create a deep copy, resolving any references for values. // 解析后的对象集合 ListPropertyValue 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; /** * 1. isWritableProperty: 属性可写 * 2. isNestedOrIndexedProperty: 是否循环嵌套 */ boolean convertible bw.isWritableProperty(propertyName) !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName); if (convertible) { // 转换器解析 convertedValue convertForProperty(resolvedValue, propertyName, bw, converter); } // Possibly store converted value in merged bean definition, // in order to avoid re-conversion for every created bean instance. 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(); } // 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); } }关键路径梳理若pvs是MutablePropertyValues且已标记isConverted()说明属性值已被提前转换过直接bw.setPropertyValues(mpvs)赋值并返回——这是避免重复转换的快捷路径否则取出PropertyValue列表创建BeanDefinitionValueResolver用于解析属性值如引用、占位符、类型化字符串逐条处理resolveValueIfNecessary解析原始值 → 判断属性可写且非嵌套属性 → 调用convertForProperty进行类型转换转换成功后将结果缓存回pv.setConvertedValue(...)这样同一定义创建的多个 Bean 实例无需重复转换全部完成后把深拷贝后的属性值统一bw.setPropertyValues(...)写入BeanWrapper。调试applyPropertyValues时可以看到图片 3propertyValueList中PropertyValue的name date原始value是TypedStringValue内容为2020-01-01 01:01:01而beanName apple——这正是待转换的原始数据。五、convertForProperty 与 doConvertTextValue转换的最后一公里convertForProperty是属性级转换的入口定义于AbstractAutowireCapableBeanFactoryNullable private Object convertForProperty( Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) { 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); } }当转换器是BeanWrapperImpl时直接调用其convertForProperty(value, propertyName)内部会读取属性描述符PropertyDescriptor拿到 setter 对应的MethodParameter与目标类型否则走通用分支convertIfNecessary(value, pd.getPropertyType(), methodParam)。最终字符串 → 对象的文本转换落在TypeConverterDelegate的doConvertTextValueprivate Object doConvertTextValue(Nullable Object oldValue, String newTextValue, PropertyEditor editor) { try { editor.setValue(oldValue); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug(PropertyEditor [ editor.getClass().getName() ] does not support setValue call, ex); } // Swallow and proceed. } // 调用子类实现方法 editor.setAsText(newTextValue); return editor.getValue(); }该方法依次执行先尝试editor.setValue(oldValue)设置旧值若编辑器不支持则吞掉异常继续调用编辑器的setAsText(newTextValue)——这是真正执行转换的子类钩子方法返回editor.getValue()得到转换后的目标对象。在本用例中setAsText会被回调到我们编写的DatePropertyEditor实现Override public void setAsText(String text) throws IllegalArgumentException { System.out.println(text); SimpleDateFormat sdf new SimpleDateFormat(format); try { Date date sdf.parse(text); this.setValue(date); } catch (Exception e) { e.printStackTrace(); } }调试断点停在doConvertTextValue中时可以清晰看到图片 4convertedValue已被转换成一个Date对象Wed Jan 01 00:00:00 CST 2020且下方代码行的standardConversion标志为false——说明该次转换走的是自定义编辑器分支而非 Spring 标准转换服务ConversionService。整个方法的返回值正是TypeConverterDelegate#convertIfNecessary(String, Object, Object, ClassT, TypeDescriptor)的处理结果。六、与 BeanFactoryPostProcessor 机制的联动CustomEditorConfigurer是BeanFactoryPostProcessor体系的一员。回顾 BeanFactoryPostProcessor.md 中的调用链容器refresh()过程中执行invokeBeanFactoryPostProcessors(...)对每个 BFPP 调用postProcessBeanFactory(beanFactory)CustomEditorConfigurer正是利用该回调把自定义属性编辑器提前注入到 Bean 工厂Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { if (this.propertyEditorRegistrars ! null) { for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) { // 把它加入Bean工厂里后面可以进行调用 beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar); } } if (this.customEditors ! null) { this.customEditors.forEach(beanFactory::registerCustomEditor); } }这与本文档第三部分的AbstractBeanFactory#registerCustomEditors正好首尾呼应BFPP 负责「注册登记」属性填充阶段才真正「消费使用」。七、全链路总结把整条链路串起来一次自定义属性转换的完整生命周期如下容器 refresh() └─ invokeBeanFactoryPostProcessors() └─ CustomEditorConfigurer.postProcessBeanFactory(beanFactory) ├─ beanFactory.addPropertyEditorRegistrar(registrar) // 方式一注册器 └─ beanFactory.registerCustomEditor(type, editor) // 方式二customEditors Bean 创建doCreateBean └─ populateBean() └─ applyPropertyValues() ├─ BeanDefinitionValueResolver.resolveValueIfNecessary() // 解析原始字符串值 ├─ convertForProperty() // 属性级转换入口 │ └─ BeanWrapperImpl.convertForProperty() │ └─ TypeConverterDelegate.convertIfNecessary() │ └─ doConvertTextValue() │ ├─ editor.setValue(oldValue) │ ├─ editor.setAsText(text) // 调用自定义转换规则 │ └─ return editor.getValue() // 得到 Date 对象 └─ bw.setPropertyValues(deepCopy) // 完成赋值值得记住的三个核心结论两类注册入口propertyEditorRegistrars编程式注册器可注册多个编辑器与customEditors类型 → 编辑器类的声明式 Map两者最终都汇入PropertyEditorRegistrySupport的customEditors映射两个存储维度按类型customEditors与按属性路径customEditorsForPath注册前者对全局该类型生效后者仅对指定属性生效一套转换模板TypeConverterDelegate#doConvertTextValue固定执行setValue→setAsText→getValue三步所有自定义PropertyEditor只需要实现setAsText即可完成字符串到任意对象的转换。如需进一步深入属性注入与类型转换的完整实现可继续阅读仓库中的 4、依赖注入(DI).md.md)其中同样包含convertForProperty、getCustomTypeConverter等方法的调用上下文以及 Spring-beanFactory.md 了解 Bean 创建的整体脉络。【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表