Spring核心——字符串到实体转换

笼统的说一个系统主要是由3个部分组成的:

执行程序:主要负责处理业务逻辑,对接用户操作。

内部数据:嵌套在源码中的数据,用于指导程序运行。

外部数据:业务数据,外部配置数据。

内部数据本身就是程序的一部分,在Java中这些数据通常停留在类的静态成员变量中。而外部数据往往与代码无关,所以对于程序而言要“读懂”它们需要进行一些前置处理。例如用户在前端页面提交的数据我们从RequestContext中获取的数据类型都是字符串,而我们的业务需要将字符串转换成数字、列表、对象等等,这就引入了我们接下来要介绍的内容——数据类型转换。

JavaBean对于J2SE或者J2EE而言有着非常重要的意义,ORACLE为了统一各个组织对JavaBean的使用方式制定了详尽的JavaBean规范,包括BeanInfoPropertyEditorPropertyEditorSupport等方面的内容。本文会涉及到JavaBean的一些规范,但是重点是介绍Spring的数据管理。

Properties结构转换为实体

标准资源文件*.properties是Java程序常用的数据存储文件,Spring提供了BeanWrapper接口将*.properties文件中的数据转换成一个标准的JavaBean对象。看下面的例子:

有一个实体类Person:

classPerson{privateString name;privateintage;privatebooleanlicense;privateDate birtday;privateAddress address;privateMap otherInfo;// Getter & Setter ......}

然后可以通过BeanWrapper将Properties对象中的数据设置到对象中:

privatevoidsimpleDataBind(){BeanWrapper wrapper =newBeanWrapperImpl(newPerson());//使用 BeanWrapper::setPropertyValue 接口设置数据wrapper.setPropertyValue("name","niubility");wrapper.setPropertyValue("age",18);wrapper.setPropertyValue("license",true);print(wrapper.getWrappedInstance());//使用 Properties对象设置数据,Properties实例可以来源于*.properties文件Properties p =newProperties();p.setProperty("name","From Properties");p.setProperty("age","25");p.setProperty("license","false");p.setProperty("otherInfo[birthday]","2000-01-01");wrapper.setPropertyValues(p);print(wrapper.getWrappedInstance());}

这样,使用Spring的BeanWrapper接口,可以快速的将Properties数据结构转换为一个JavaBean实体。

除了配置单个实体的数据,BeanWrapper还可以为嵌套结构的实体设置数据。现在增加一个实体Vehicle:

publicclassVehicle{privateString name;privateString manufacturer;privatePerson person;//Person对象// Getter & Setter ......}

在Vehicle中有一个Person类型的成员变量(person域),我们可以利用下面具备嵌套结构的语法来设置数据:

privateBeanManipulationAppnestedDataBind(){// 数据嵌套转换BeanWrapper wrapper =newBeanWrapperImpl(newVehicle(newPerson()));Properties p =newProperties();p.setProperty("name","Envision");p.setProperty("manufacturer","Buick");//person.name表示设置person域的name变量数值p.setProperty("person.name","Alice");p.setProperty("person.age","25");p.setProperty("person.license","true");p.setProperty("person.otherInfo[license code]","123456789");wrapper.setPropertyValues(p);print(wrapper.getWrappedInstance());returnthis;}

在*.properties文件中,经常使用path.name=param的的语法来指定一个嵌套结构(比如LOG4J的配置文件),这里也使用类似的方式来指定嵌套结构。person.name在程序执行时会调用Vehicle::getPerson::setName方法来设定数据。

除了设定单个数据BeanWrapper还提供了更丰富的方法来设置数据,以上面的Vehicle、person为例:

表达式效果

p.setProperty("name", "Envision")name域的数据设置为"Envision"

p.setProperty("person.name", "Alice")将嵌套的person域下的name数据设置为"Alice"

p.setProperty("list[1]", "Step2")list域是一个列表,将第二个数据设置为"Step2"

p.setProperty("otherInfo[birthday]", "2000-01-01")otherInfo域是一个Map,将key=birthday、value="2000-01-01"的数据添加到Map中。

上面这4条规则可以组合使用,比如p.setProperty("person.otherInfo[license code]", "123456789")。

关于在Java如何使用Properties有很多讨论(比如这篇stackoverflow的问答),BeanWrapper不仅仅是针对资源文件,他还衍生扩展了数据类型转换等等功能。

PropertyEditor转换数据

在JavaBean规范中定义了java.beans.PropertyEditor,他的作用简单的说就是将字符串转换为任意对象结构。

PropertyEditor最早是用来支持java.awt中的可视化接口编辑数据的(详情见Oracle关于IDE数据定制化的介绍)。但是在Spring或其他应用场景中更多的仅仅是用来做字符串到特定数据格式的转换(毕竟java.awt应用不多),所以PropertyEditor提供的BeanWrapper::paintValue之类的支持awt的方法不用太去关心他,主要聚焦在BeanWrapper::setAsText方法上。

BeanWrapper继承了PropertyEditorRegistry接口用于注册PropertyEditor。BeanWrapperImpl已经预设了很多有价值的PropertyEditor,比如上面的例子的代码p.setProperty("age", "25");,age域是一个数字整型,而Properties中的数据都是字符串,在设置数据时会自动启用CustomNumberEditor将字符串转换为数字。

Spring已经提供的PropertyEditor可以看这里的清单。需要注意的是,这些PropertyEditor并不是每一个都默认启用,比如CustomDateEditor必须由开发者提供DateFormat才能使用,所以需要像下面这样将其添加注册到BeanWrapper中:

privatevoidpropertyEditor(){BeanWrapper wrapper =newBeanWrapperImpl(newPerson());// 设定日期转换格式DateFormat df =newjava.text.SimpleDateFormat("yyyy-MM-dd");// 将Editor与DateFormat进行帮顶,使用指定的格式CustomDateEditor dateEditor =newCustomDateEditor(df,false);// 注册dateEditor,将其与Date类进行绑定wrapper.registerCustomEditor(Date.class, dateEditor);// CustomNumberEditor执行转换wrapper.setPropertyValue("age","18");// CustomBooleanEditor执行转换wrapper.setPropertyValue("license","false");// dateEditor执行转换wrapper.setPropertyValue("birtday","1999-01-30");print(wrapper.getWrappedInstance());}

添加之后,设定setPropertyValue("birtday", "1999-01-30")时会自动使用指定的DateFormat转换日期。

自定义PropertyEditor

除了预设的各种PropertyEditor,我们还可以开发自定义的PropertyEditor。Person中有一个类型为Address的成员变量:

publicclassAddress{privateString province;//省privateString city;//市privateString district;//区// Getter & Setter}

我们为Address实体添加一个PropertyEditor,将特定格式的字符串转换为Address结构:

publicclassAddressEditorextendsPropertyEditorSupport{privateString[] SPLIT_FLAG = {",","-",";",":"};publicvoidsetAsText(String text){intpos = -1;Address address =newAddress();for(String flag : SPLIT_FLAG) {pos = text.indexOf(flag);if(-1< pos) {String[] split = text.split(flag);address.setProvince(split[0]);address.setCity(split[1]);address.setDistrict(split[2]);break;}}if(-1== pos) {thrownewIllegalArgumentException("地址格式错误");}setValue(address);//设定Address实例}}

通过AddressEditor::setAsText方法,可以将输入的字符串最红转换为一个Address实例。通常情况下开发一个Editor转换器不会直接去实现PropertyEditor接口,而是继承PropertyEditorSupport。

然后我们使用AddressEditor来将字符串转换为Address对象:

privateBeanManipulationApppropertyEditor(){//使用预设转换工具和自定义转换工具BeanWrapper wrapper =newBeanWrapperImpl(newPerson());// 创建AddressEditor实例AddressEditor addressEditor =newAddressEditor();// 注册addressEditor,将其与Address类进行绑定wrapper.registerCustomEditor(Address.class, addressEditor);// 设置值自动进行转化wrapper.setPropertyValue("address","广东-广州-白云");print(wrapper.getWrappedInstance());}

按照JavaBean规范,PropertyEditor和对应的JavaBean可以使用命名规则来表示绑定关系,而无需显式的调用注册方法。

绑定的规则是:有一个JavaBean命名为Tyre,在相同的包下(package)有一个实现了PropertyEditor接口并且命名为TyreEditor的类,那么框架认为TyreEditor就是Tyre的Editor,无需调用BeanWrapper::registerCustomEditor方法来声明Tyre和TyreEditor的绑定关系,详情请看源码中chkui.springcore.example.hybrid.beanmanipulation.bean.Tyre的使用。

IoC与数据转换整合

对于Spring的ApplicationContext而言,BeanWrapper、PropertyEditor都是相对比较底层的功能,在使用Spring Ioc容器的时候可以直接将这些功能嵌入到Bean初始化中或MVC的requestContext的数据转换中。

从框架使用者的角度来看,Spring的XML配置数据或者通过MVC接口传递数据都是字符串,因此PropertyEditor在处理这些数据时有极大的用武之地。IoC容器使用后置处理器CustomEditorConfigurer来管理Bean初始化相关的PropertyEditor。通过CustomEditorConfigurer可以使用所有预设的Editor,还可以增加自定义的Editor,下面是使用@Configurable启用CustomEditorConfigurer的例子:

@Configurable@ImportResource("classpath:hybrid/beanmanipulation/config.xml")publicclassBeanManipulationConfig{@BeanCustomEditorConfigurercustomEditorConfigurer(){// 构建CustomEditorConfigurerCustomEditorConfigurer configurer =newCustomEditorConfigurer();Map, Class> customEditors =newHashMap<>();// 添加AddressEditor和Address的绑定customEditors.put(Address.class, AddressEditor.class);// 添加绑定列表configurer.setCustomEditors(customEditors);// 通过PropertyEditorRegistrar注册PropertyEditorconfigurer.setPropertyEditorRegistrars(newPropertyEditorRegistrar[] {newDateFormatRegistrar() });returnconfigurer;}}

CustomEditorConfigurer::setCustomEditorsCustomEditorConfigurer::setPropertyEditorRegistrars都可以向容器中添加PropertyEditor,最主要区别在于:

前者是直接申明一对绑定关系的类对象(Class),例如customEditors.put(Address.class, AddressEditor.class); 这行代码并没有实例化AddressEditor,而是将实例化交给后置处理器。

而后者是提供一个实例化的PropertyEditor,比前者更能实现更复杂的功能。比如下面的DateFormatRegistrar代码,由于需要组装DateFormat和CustomDateEditor,所以使用PropertyEditorRegistrar来实现这个过程更加合理,后置处理器会在某个时候调用这个注册方法。

publicclassDateFormatRegistrarimplementsPropertyEditorRegistrar{@OverridepublicvoidregisterCustomEditors(PropertyEditorRegistry registry){DateFormat df =newjava.text.SimpleDateFormat("yyyy-MM-dd");CustomDateEditor editor =newCustomDateEditor(df,false);registry.registerCustomEditor(Date.class, editor);}}

配置好CustomEditorConfigurer之后,就可以直接在配置Bean的时候直接使用预定的格式了,比如:

<!-- 使用CustomNumberEditor转换 --><!-- 使用CustomBooleanEditor转换 --><!-- 使用CustomDateEditor转换 --><!-- 使用AddressEditor转换 -->

此外,在Spring MVC中,可以SimpleFormController::initBinder方法将外部传入的数据和某个Bean进行绑定:

publicfinalclassMyControllerextendsSimpleFormController{// 通过任何方式获取PropertyEditorRegistrar@AutowiredprivateMyPropertyEditorRegistrar editorRegistrar;protectedvoidinitBinder(HttpServletRequest request,

            ServletRequestDataBinder binder)throwsException{// 将Editor与当前Controller进行绑定this.editorRegistrar.registerCustomEditors(binder);    }}

Spring MVC并不属于Sring核心功能范畴,这里就不展开了,需要了解的话看看SimpleFormController的JavaDoc文档即可。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容