17. Spring类型转换之PropertyEditor

简介

我们之所以使用spring如此的简单,并且功能比较强大,比较人性化,是因为很多重复且繁琐的以及能想到的可以简化业务的功能都给我们默认提供了,类型转化就是典型的例子

spring提供了两种类型转化的方式,一种是spring提供的ConversionService,后面一篇文章再讲,另外一种就是本文要介绍的
PropertyEditor,它是jdk提供的 java.beans.PropertyEditor,spring自然也要支持

快速使用

一般的转换spring都提供了,但也不排除依然不满足我们的需要,假如现在有一个User类,使用@Value注入一个字符串,就能得到一个User,并且User的name属性就是注入的值,显然spring不知道我们有一个User类,那么就只能我们自己去实现

先看怎么使用,在分析源码

定义一个要转换的类

public class User {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}

定义转换器

public class String2UserPropertyEditor extends PropertyEditorSupport implements PropertyEditor {@Overridepublic void setAsText(String text) throws IllegalArgumentException {User user = new User(); // 直接实例化一个Useruser.setName(text); // 设置到Namethis.setValue(user);}
}

告诉spring我们的转换器

@Configuration
public class ConvertConfig {@Beanpublic static CustomEditorConfigurer customEditorConfigurer() {CustomEditorConfigurer configurer = new CustomEditorConfigurer();Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>();customEditors.put(User.class, String2UserPropertyEditor.class);configurer.setCustomEditors(customEditors);return configurer;}
}

测试


@Component
public class UserBean implements UserGenerics {@Value("xx")private User user;public User getUser() {return user;}
}public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);UserBean userBean = context.getBean("userBean", UserBean.class);System.out.println(userBean);System.out.println(userBean.getUser());System.out.println(userBean.getUser().getName());
}输出com.shura.beans.UserBean@668bc3d5
com.shura.convert.User@3cda1055
xx

从上面结果可以看出确实spring给我们自动转换了。

下面就从源码看看,如何告诉spring我们需要的转换器以及spring提供的默认转换器

首先看 CustomEditorConfigurer

源码分析

public class CustomEditorConfigurer implements BeanFactoryPostProcessor, Ordered {protected final Log logger = LogFactory.getLog(getClass());private int order = Ordered.LOWEST_PRECEDENCE;@Nullableprivate PropertyEditorRegistrar[] propertyEditorRegistrars;@Nullableprivate Map<Class<?>, Class<? extends PropertyEditor>> customEditors;public void setOrder(int order) {this.order = order;}@Overridepublic int getOrder() {return this.order;}// 添加类型转换注册器 先忽略public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {this.propertyEditorRegistrars = propertyEditorRegistrars;}// 添加类型转换器public void setCustomEditors(Map<Class<?>, Class<? extends PropertyEditor>> customEditors) {this.customEditors = customEditors;}// BeanFactory后置处理@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {if (this.propertyEditorRegistrars != null) {for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar);}}// 将类型转换器设置到beanFactoryif (this.customEditors != null) {this.customEditors.forEach(beanFactory::registerCustomEditor);}}}

CustomEditorConfigurer的代码比较简单,就是将我们自定义的类型转换器设置到beanFactory中,知道它是一个BeanFactoryPostProcessor就好理解了,肯定在Bean实例化之前BeanFactory就有了各种类型转换器了

spring实例化Bean的时候将每个Bean都先实例化一个 BeanWrapperImpl
org.springframework.beans.factory.support.ConstructorResolver#instantiateUsingFactoryMethod

这个类很重要,我们需要记住BeanWrapperImpl是 TypeConverterSupport PropertyEditorRegistry PropertyEditorRegistrySupport
TypeConverter的子类,并且有一个属性 TypeConverterDelegate 用来真正做转换用的

BeanWrapperImpl -> TypeConverterSupport (一个属性 TypeConverterDelegate)


// ConstructorResolver类
public BeanWrapper instantiateUsingFactoryMethod({BeanWrapperImpl bw = new BeanWrapperImpl();this.beanFactory.initBeanWrapper(bw);...
}// AbstractBeanFactory类
protected void initBeanWrapper(BeanWrapper bw) {bw.setConversionService(getConversionService()); // 下节分析registerCustomEditors(bw); // 注册PropertyEditor 
}protected void registerCustomEditors(PropertyEditorRegistry registry) {if (registry instanceof PropertyEditorRegistrySupport) {((PropertyEditorRegistrySupport) registry).useConfigValueEditors();}if (!this.customEditors.isEmpty()) {this.customEditors.forEach((requiredType, editorClass) ->registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass))); // 添加到了PropertyEditorRegistrySupport}
}

上面源码简单来说就是将beanFactory中自定义的PropertyEditor添加到了BeanWrapper

上面提到了BeanWrapper里面会有一个TypeConverterDelegate属性,他就是真正做转换的类,中间细节不讲了,直接看怎么转的

public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {// 在这一步根据requiredType=User 就可以找到我们定义的 String2UserPropertyEditor PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);// ... 跳过 ConversionService ...Object convertedValue = newValue;if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {// 如果没有找到就从默认的propertyEditor去找if (editor == null) {editor = findDefaultEditor(requiredType);}// 转换convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);}return (T) convertedValue;
}

从上面代码可以看出就是找出前面注册的PropertyEditor,然后进行转换,转换的逻辑很简单,setText()执行一下就有了对象,然后执行getText拿到对象就行

如果没有自定义的就从默认的去找,下面就看看有哪些默认的

private PropertyEditor findDefaultEditor(@Nullable Class<?> requiredType) {return this.propertyEditorRegistry.getDefaultEditor(requiredType);;
}public PropertyEditor getDefaultEditor(Class<?> requiredType) {if (this.defaultEditors == null) {createDefaultEditors();}return this.defaultEditors.get(requiredType);
}private void createDefaultEditors() {this.defaultEditors = new HashMap<>(64);this.defaultEditors.put(Charset.class, new CharsetEditor());this.defaultEditors.put(Class.class, new ClassEditor());this.defaultEditors.put(Class[].class, new ClassArrayEditor());this.defaultEditors.put(Currency.class, new CurrencyEditor());this.defaultEditors.put(File.class, new FileEditor());this.defaultEditors.put(InputStream.class, new InputStreamEditor());if (!shouldIgnoreXml) {this.defaultEditors.put(InputSource.class, new InputSourceEditor());}this.defaultEditors.put(Locale.class, new LocaleEditor());this.defaultEditors.put(Path.class, new PathEditor());this.defaultEditors.put(Pattern.class, new PatternEditor());this.defaultEditors.put(Properties.class, new PropertiesEditor());this.defaultEditors.put(Reader.class, new ReaderEditor());this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());this.defaultEditors.put(URI.class, new URIEditor());this.defaultEditors.put(URL.class, new URLEditor());this.defaultEditors.put(UUID.class, new UUIDEditor());this.defaultEditors.put(ZoneId.class, new ZoneIdEditor());// Default instances of collection editors.// Can be overridden by registering custom instances of those as custom editors.this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));// Default editors for primitive arrays.this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());// The JDK does not contain a default editor for char!this.defaultEditors.put(char.class, new CharacterEditor(false));this.defaultEditors.put(Character.class, new CharacterEditor(true));// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));// The JDK does not contain default editors for number wrapper types!// Override JDK primitive number editors with our own CustomNumberEditor.this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));// 提供了string转数组的逻辑,我们通过配置地址的时候就会使用到if (this.configValueEditorsActive) {StringArrayPropertyEditor sae = new StringArrayPropertyEditor();this.defaultEditors.put(String[].class, sae);this.defaultEditors.put(short[].class, sae);this.defaultEditors.put(int[].class, sae);this.defaultEditors.put(long[].class, sae);}
}

以上就是默认PropertyEditor的逻辑

下一篇我们介绍另外一种类型转换器 ConversionService


欢迎关注,学习不迷路!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/148651.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零开始:Rust环境搭建指南

大家好&#xff01;我是lincyang。 今天&#xff0c;我们将一起探讨如何从零开始搭建Rust开发环境。 Rust环境搭建概览 Rust是一种系统编程语言&#xff0c;以其安全性、并发性和性能闻名。搭建Rust环境是学习和使用这一语言的第一步。 第一步&#xff1a;安装Rust Rust的…

3-docker安装centos7

CentOS7.9下安装完成docker后&#xff0c;后续我们可以在其上安装centos7系统。具体操作如下&#xff1a; 1.以root用户登录CentOS7.9服务器&#xff0c;拉取centos7 images 命令&#xff1a; docker pull centos:centos7 2.加载centos7 images并登录验证 命令&#xff1a;…

Activiti7工作流

文章目录 一、工作流介绍1.1 概念1.2 适用行业1.3 应用领域1.4 传统实现方式1.5 什么是工作流引擎 二、什么是Activiti7&#xff1f;2.1 概述2.2 Activiti7内部核心机制2.3 BPMN2.4 Activiti如何使用2.4.1 整合Activiti2.4.2 业务流程建模2.4.3 部署业务流程2.4.4 启动流程实例…

python语法之变量名

变量可以有一个简短的名称(如x和y)&#xff0c;也可以有一个更具描述性的名称(如age、carname、total_volume)。Python变量规则: 变量名必须以字母或下划线开头变量名不能以数字开头变量名只能包含字母数字字符和下划线(A-z、0-9和_)。变量名区分大小写(age、age和age是三个不…

WPF中行为与触发器的概念及用法

完全来源于十月的寒流&#xff0c;感谢大佬讲解 一、行为 (Behaviors) behaviors的简单测试 <Window x:Class"Test_05.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winf…

DevToys:开发者的多功能瑞士军刀,让编程更高效!

DevToys&#xff1a;开发者的多功能瑞士军刀&#xff0c;让编程更高效&#xff01; DevToys 是一款专为开发者设计的实用工具&#xff0c;它能够帮助用户完成日常的开发任务&#xff0c;如格式化 JSON、比较文本和测试正则表达式&#xff08;RegExp&#xff09;。它的优势在于…

Selenium UI 自动化

一、Selenium 自动化 1、什么是Selenium&#xff1f; Selenium是web应用中基于UI的自动化测试框架。 2、Selenium的特点&#xff1f; 支持多平台、多浏览器、多语言。 3、自动化工作原理&#xff1f; 通过上图&#xff0c;我们可以注意到3个角色&#xff0c;下面具体讲解一…

VBA之Word应用:文档(Document)的书签

《VBA之Word应用》&#xff08;版权10178982&#xff09;&#xff0c;是我推出第八套教程&#xff0c;教程是专门讲解VBA在Word中的应用&#xff0c;围绕“面向对象编程”讲解&#xff0c;首先让大家认识Word中VBA的对象&#xff0c;以及对象的属性、方法&#xff0c;然后通过实…

大数据数仓建模基础理论【维度表、事实表、数仓分层及示例】

文章目录 什么是数仓仓库建模&#xff1f;ER 模型三范式 维度建模事实表事实表类型 维度表维度表类型 数仓分层ODS 源数据层ODS 层表示例 DWD 明细数据层DWD 层表示例 DIM 公共维度层DIM 层表示例 DWS 数据汇总层DWS 层表数据 ADS 数据应用层ADS 层接口示例 数仓分层的优势 什么…

数据结构之链表练习与习题详细解析

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 数据结构初阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 1.前言 2.习题解…

CICD 持续集成与持续交付——git

git使用 [rootcicd1 ~]# yum install -y git[rootcicd1 ~]# mkdir demo[rootcicd1 ~]# cd demo/ 初始化版本库 [rootcicd1 demo]# git init 查看状态 [rootcicd1 demo]# git status[rootcicd1 demo]# git status -s #简化输出 [rootcicd1 demo]# echo test > README.md[roo…

python自动化标注工具+自定义目标P图替换+深度学习大模型(代码+教程+告别手动标注)

省流建议 本文针对以下需求&#xff1a; 想自动化标注一些目标不再想使用yolo想在目标检测/语意分割有所建树计算机视觉项目想玩一玩大模型了解自动化工具了解最前沿模型自定义目标P图替换… 确定好需求&#xff0c;那么我们发车&#xff01; 实现功能与结果 该模型将首先…

Python中的迭代器、生成器和装饰器

当谈到Python中的迭代器、生成器和装饰器时&#xff0c;这三个概念都是与函数和数据处理密切相关的。让我们逐个深入了解它们。 1. 迭代器&#xff08;Iterators&#xff09;&#xff1a; 迭代器是一个可以逐个访问元素的对象。在Python中&#xff0c;迭代器实现了两个方法&a…

6 Redis的慢查询配置原理

1、redis的命令执行流程 redis的慢查询只针对步骤3 默认情况下&#xff0c;慢查询的阈值是10ms

基于PHP+MySql的酒店信息管理系统的设计与实现

一、系统开发环境 运行环境&#xff1a;phpstudy或者wampserver&#xff0c; 开发工具&#xff1a;vscodephpstorm 数据库&#xff1a;mysql 二、酒店管理系统功能 1.前台功能&#xff1a; 首页客房推荐&#xff0c;周边特色介绍 酒店在线预订 订单查询&#xff0c;可以…

C++各种字符转换

C各种字符转换 一.如何将char数组转化为string类型二. string转char数组&#xff1a;参考 一.如何将char数组转化为string类型 在C中&#xff0c;可以使用string的构造函数或者赋值操作符来将char数组转换为string类型。 方法1&#xff1a;使用string的构造函数 const char* c…

【Web】Ctfshow SSTI刷题记录1

目录 ①web361 362-无过滤 ②web363-过滤单双引号 ③web364-过滤单双引号和args ④web365-过滤中括号[]、单双引号、args ⑤web366-过滤单双引号、args、中括号[]、下划线 ⑦web367-过滤单双引号、args、中括号[]、下划线、os ⑧web368-过滤单双引号、args、中括号[]、下…

原理Redis-动态字符串SDS

动态字符串SDS Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。 不过Redis没有直接使用C语言中的字符串&#xff0c;因为C语言字符串存在很多问题&#xff1a; 获取字符串长度的需要通过运算非二进制安全…

qt-C++笔记之两个窗口ui的交互

qt-C笔记之两个窗口ui的交互 code review! 文章目录 qt-C笔记之两个窗口ui的交互0.运行1.文件结构2.先创建widget项目&#xff0c;搞一个窗口ui出来3.项目添加第二个widget窗口出来4.补充代码4.1.qt_widget_interaction.pro4.2.main.cpp4.3.widget.h4.4.widget.cpp4.5.second…

ClickHouse数据一致性

查询CK手册发现&#xff0c;即便对数据一致性支持最好的Mergetree&#xff0c;也只是保证最终一致性&#xff1a; 我们在使用 ReplacingMergeTree、SummingMergeTree 这类表引擎的时候&#xff0c;会出现短暂数据不一致的情况。 在某些对一致性非常敏感的场景&#xff0c;通常有…