有同事和我讨论了一个问题:怎样把Date转换成正确的格式给前端?当然可以手动转换,但是他更期望通过框架配置的方式解决。关于这个问题有两种思路:1、在orm框架中转换;2、在json框架中转换。在这里我想分享一下自己的理解和经验。
谁可以做?
类型转换不是什么神奇的事,看源码就会发现,它只是一堆的if else。如果是基本类型怎么转,如果是枚举类怎么转,如果是日期怎么转,如果是普通Object怎么转。也就是各种的type handler。只要做了类型转换这件事,正常一点的框架,很自然都会支持配置这种handler,以及新增handler。
FastJson的代码片段
if (writer == null) { if (Map.class.isAssignableFrom(clazz)) { config.put(clazz, MapSerializer.instance); } else if (List.class.isAssignableFrom(clazz)) { config.put(clazz, ListSerializer.instance); } else if (Collection.class.isAssignableFrom(clazz)) { config.put(clazz, CollectionSerializer.instance); } else if (Date.class.isAssignableFrom(clazz)) { config.put(clazz, DateSerializer.instance); } else if (JSONAware.class.isAssignableFrom(clazz)) { config.put(clazz, JSONAwareSerializer.instance); } else if (JSONStreamAware.class.isAssignableFrom(clazz)) { config.put(clazz, JSONStreamAwareSerializer.instance); } else if (clazz.isEnum() || (clazz.getSuperclass() != null && clazz.getSuperclass().isEnum())) { config.put(clazz, EnumSerializer.instance); } else if (clazz.isArray()) { Class<?> componentType = clazz.getComponentType(); ObjectSerializer compObjectSerializer = getObjectWriter(componentType); config.put(clazz, new ArraySerializer(componentType, compObjectSerializer)); } else if (Throwable.class.isAssignableFrom(clazz)) { config.put(clazz, new ExceptionSerializer(clazz)); } else if (TimeZone.class.isAssignableFrom(clazz)) { config.put(clazz, TimeZoneCodec.instance); } else if (Appendable.class.isAssignableFrom(clazz)) { config.put(clazz, AppendableSerializer.instance); } else if (Charset.class.isAssignableFrom(clazz)) { config.put(clazz, CharsetCodec.instance); } else if (Enumeration.class.isAssignableFrom(clazz)) { config.put(clazz, EnumerationSeriliazer.instance); } else if (Calendar.class.isAssignableFrom(clazz)) { config.put(clazz, CalendarCodec.instance); } else if (Clob.class.isAssignableFrom(clazz)) { config.put(clazz, ClobSeriliazer.instance); } else { boolean isCglibProxy = false; boolean isJavassistProxy = false; for (Class<?> item : clazz.getInterfaces()) { if (item.getName().equals("net.sf.cglib.proxy.Factory")) { isCglibProxy = true; break; } else if (item.getName().equals("javassist.util.proxy.ProxyObject")) { isJavassistProxy = true; break; } } if (isCglibProxy || isJavassistProxy) { Class<?> superClazz = clazz.getSuperclass(); ObjectSerializer superWriter = getObjectWriter(superClazz); config.put(clazz, superWriter); return superWriter; } if (Proxy.isProxyClass(clazz)) { config.put(clazz, config.createJavaBeanSerializer(clazz)); } else { config.put(clazz, config.createJavaBeanSerializer(clazz)); } }
ORM or JSON?
java bean中的字段,反映的应该是该字段的最原始的类型。日期就应该是Date,枚举值就应该是枚举类型。如何把它在数据库中正确地存取,是orm框架的事;如何正确地转换成字符串或者从字符串解析,是json框架的事。所以针对“把Date转换成正确的格式给前端”这件事,我理解还是由json框架来做比较合适。
GSON
gson要自定义配置可通过GsonBuilder来构造Gson对象。
示例代码
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create(); String jsonString = gson.toJson(eventProcess);
FASTJSON
阿里的fastjson的自定义转换格式的处理方式更为简单明了,只需使用com.alibaba.fastjson.annotation.JSONField注解。
示例代码
@JSONField(format = "yyyy-MM-dd HH:mm:ss") private Date createTime;
Mybatis
Mybatis可以在mybatis-config.xml中定义自己的typeHandlers。下面的代码示例中,java bean的字段为枚举类型,数据库存储为数字,使用枚举对象的getOrder方法值进行转换。
mybatis-config.xml
<typeHandlers> <typeHandler handler="com.common.mybatis.EnumTypeOrderHandler" javaType="com.massage.enumType.OrderStatus"/> </typeHandlers>
com.common.mybatis.EnumTypeOrderHandler
public class EnumTypeOrderHandler<E extends Enum<E>> extends BaseTypeHandler<E> { private Class<E> type; private final E[] enums; public EnumTypeOrderHandler(Class<E> type) { if (type == null) throw new IllegalArgumentException("Type argument cannot be null"); this.type = type; this.enums = type.getEnumConstants(); if (this.enums == null) throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type."); } @Override public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException { int order = parameter.ordinal(); try { Method getOrder = type.getMethod("getOrder"); order = (int) getOrder.invoke(parameter); } catch (Exception e) { } ps.setInt(i, order); } @Override public E getNullableResult(ResultSet rs, String columnName) throws SQLException { int order = rs.getInt(columnName); if (rs.wasNull()) { return null; } else { return parseOrder(order); } } @Override public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException { int order = rs.getInt(columnIndex); if (rs.wasNull()) { return null; } else { return parseOrder(order); } } @Override public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { int order = cs.getInt(columnIndex); if (cs.wasNull()) { return null; } else { return parseOrder(order); } } private E parseOrder(int order) throws SQLException { try { for (E anEnum : enums) { try { Method getOrder = type.getMethod("getOrder"); int anOrder = (int) getOrder.invoke(anEnum); if (anOrder == order) { return anEnum; } } catch (Exception e) { return enums[order]; } } } catch (Exception ex) { throw new IllegalArgumentException("Cannot convert " + order + " to " + type.getSimpleName() + " by ordinal value.", ex); } throw new IllegalArgumentException("Cannot convert " + order + " to " + type.getSimpleName() + " by ordinal value."); } }
Spring
说到Spring,大家想到的就是ioc和aop。然而spring并不只有ioc和aop。这些年来Spring家出了很多产品,都是基于Spring核心API,也都天然地扩展性相当之强。Spring似乎提供了做一个基础框架所需的一切,包括类型转换(参见http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#validation一章)。比如,SpringMVC接收请求参数转为bean,Spring Batch的Reader读取记录为bean,都用到了PropertyEditor。
自定义TimeEditor
public class TimeEditor extends PropertyEditorSupport { private String format = "H:mm"; public TimeEditor() { } public TimeEditor(String format) { this.format = format; } public void setAsText(String text) { DateFormat df = new SimpleDateFormat(format); try { Date date = df.parse(text); Time time = new Time(date.getTime()); setValue(time); } catch (ParseException e) { throw new RuntimeException(e); } } }
在Controller中使用自定义PropertyEditor来解析时间格式
public Entity parseModel(HttpServletRequest request) { Object o = null; try { o = getEntityClass().newInstance(); } catch (Exception e) { throw new RuntimeException("无法创建类的实例:" + getEntityClass()); } DataBinder dataBinder = new DataBinder(o); dataBinder.registerCustomEditor(Time.class, new TimeEditor()); MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request); dataBinder.bind(mpvs); return (Entity) o; }