使用统一转换服务来处理不同数据展现的思路和实现

本文示例代码:https://github.com/flym/train-propertytranslate

本文描述了这样一个场景:
针对于一个功能场景,第三方过来的数据格式均不相同,但需要通过一个统一的功能接口来进行调用,然后根据不同来源数据格式进行不同的数据展现。在后端实现时,尽量不要通过if else进行硬编码,而是通过配置的方式来完成数据的处理和呈现。

场景中提到了几个概念,如下:

  • 功能场景和数据来源:分组信息,针对同一个来源,其格式是相同的。不同来源的数据格式不一样
  • 统一功能接口:调用入口是统一的,即方法名,参数定义,以及返回格式都是相同的,仅参数内容不相同
  • 配置化:场景是通用的,可以通过配置来实现,而不是在业务代码中硬编码
  • 不同的数据展现:可以针对不同的分组和场景通过模板来进行渲染,而模板本身是可以配置的,这样即隔离了数据封装这一层。

举例如下, 如下的一个数据内容:

{
    "trade_fullinfo_get_response": {
        "trade": {
            "seller_nick": "我在测试",
            "pic_name": "T1jVXXXePbXXaoPB6a_091917.jpg",

            "receiver_name": "东方不败",
            "buyer_message": "要送的礼物的,不要忘记的哦",
            "receiver_city": "杭州市",
            "receiver_district": "西湖区",
            "orders": {
                "order": [
                    {
                        "title": "苹果",
                        "unit": "个",
                        "oid": 1,
                        "price": 1.1,
                        "sum": 1
                    }
                ]
            },
        }
    }
}

通过转换服务处理,可以展现为下面两种不同的数据内容(以html渲染为例)

渲染场景1
渲染场景2

本文从数据格式,转换器,转换过程,数据渲染几个方面来描述这一思路.

继续阅读“使用统一转换服务来处理不同数据展现的思路和实现”

使用spring typeConverter造成的数据错乱(多线程环境)

在线上环境碰到一个问题,经由数据库查询并进行数据处理转换之后的数据在界面显示时随机出现数据错位。由于该问题不可重现(重新清除缓存并操作一次问题即解决),并且由于缓存的存在(缓存了错误的数据),导致此问题严重并且很难查找。

业务场景描述如下:

数据库查询数据->一次处理->类型转换->二次处理->界面显示

由于每个步骤都涉及到很多代码,因此在处理时通过在不同的点设定潜在出错点,并通过判断数据变化进行log。尝试在本地环境重现此问题。经过一次偶然的场景,发现一个本该是8位数字的字符串在转换过程中报错,由此找出了真正的问题。

//报错的业务代码 假定数据为 20141111
str = s.substring(0,4) + "/" + s.substring(4,6) + "/" + s.substring(6,8)
//报错异常 indexOutofBound,即字符串不足8位...

经过断点,加层层回溯,终于发现数据在经过一次类型转换之后被修改了。转换代码如下所示:

private static TypeConverter typeConverter = new SimpleTypeConverter();
        public <T> T convertValue(Object value) {
            return (T)typeConverter.convertIfNecessary(value, Integer.class);
        }

初看起来,这个方法调用并没有什么问题,即将数据值转换为整数类型.重点的问题在于在这个方法(由spring提供)的内部实现并不是线程安全的。附API说明:

public interface TypeConverter
Interface that defines type conversion methods. Typically (but not necessarily) implemented in conjunction with the PropertyEditorRegistry interface.
Note: Since TypeConverter implementations are typically based on PropertyEditors which aren't thread-safe, TypeConverters themselves are not to be considered as thread-safe either.

typeConverter内部会使用propertyEditor来进行类型转换。这种转换在spring3.0之后已不再推荐使用。见 使用线程安全的spring类型转换器ConversionService VS TypeConverter

解决方法也简单,将其更换为conversionService即可(为什么不自己写?不想重复发明轮子)。spring3.0之后,为conversionService内置了常见类型之间的转换,相当于整个类型转换子框架被完全重写了,重写的转换是状态无关的,即线程安全。至于原来的typeConverter及propertyEditor,忘掉它吧,遗留的东西。

附:jdk自带的simpleDateFormat也存在相同的问题,用这个类需要小心处理线程问题。

使用线程安全的spring类型转换器ConversionService VS TypeConverter

此文翻译源:http://www.theserverside.com/tip/Spring-Converters-and-Formatters。部分无用的部分有删除

1 类型转换接口

在spring3.0之前,如果要自己实现一个从字符串到其它对象的转换,那么就需要实现PropertyEditor接口。PropertyEditor是遵循javaBean规范的属性处理器,其通过set方法设置属性值,通过get方法获取属性值,相应的转换逻辑就隐藏于其中。这个接口惟一的问题在于,他们不是线程安全的.并且只能实现从字符串到其它对象的转换。

在spring3.0,提供了一个更简单的类型转换器接口。在spring mvc中,可以使用内置的或者自己实现的接口来实现从请求参数到对象之间的转换。其相应的接口定义如下:

public interface Converter<S,T> {
    public T convert(S source);
}

即通过一个S类型的对象,将其转换为T类型的对象。其中S类型并不局限于字符串,可以是任何类型。这是与PropertyEditor的主要区别。

继续阅读“使用线程安全的spring类型转换器ConversionService VS TypeConverter”