背景
选用SpringCloud框架搭建微服务做业务后盾利用时,会波及到大量的业务状态值定义,个别惯例做法是:
- 长久层(数据库)存储int类型的值
- 后盾零碎里用浏览性好一点儿的常量将int类型的值做一层映射
- 前端(app或浏览器)同样定义一套常量去映射这些关系
- 前端调用后盾零碎的接口时,应用常量定义的int类型进行提交
源于长久层存储的优化规定,int类型要比varchar类型效率高很多,这套做法也是大家接受度十分高的。
只是这里有一个不是很不便的中央:状态值映射的常量定义波及前端和后盾两局部,沟通的老本是一方面,另外如果状态值有变动,须要两组人员同时批改。
预期指标
在保障长久层的int类型存储状态值的前提下,次要是思考业务状态的可浏览性问题和多处批改的问题,可浏览性问题一部分能够通过前后端人员定义常量来解决,但接口调试时还是间接应用int类型,这部分的可浏览性问题还是存在,多处批改的问题须要重点解决。
本篇举荐的计划:
- 长久层(数据库)存储没用原先的int类型值,这点放弃不变
- 后盾零碎应用enum定义业务状态,不同的业务状态集能够由多个enum来实现,enum反对国际化
- 前端展现enum国际化的文本内容
- 前端调用后盾零碎接口时,应用enum国际化的文本内容进行提交
- 后盾接管enum国际化的文本内容转换成int类型值,存储在数据库
计划的长处:
- 长久层原有的设计,效率性问题不受影响
- 业务状态的定义、映射全副内聚到后盾零碎,后续有状态值变动时,只需后盾做相应批改即可
- 前端展现的内容,接口传输的内容均为浏览性更好的文本,并且反对国际化
计划的毛病:
- 后盾零碎存储、读取状态值时,须要用enum进行转换
- 通信传输的内容报文比原有的int类型大一点点
计划实际
实际原理
此实际计划次要蕴含三局部:
- Enum类应用Jackson进行JSON序列化和反序列化
- Enum枚举项的messages国际化解决
- Enum的定义
Enum自定义序列化和反序列化
先定义Enum国际化类,自定义Enum的序列化和反序列化类,并应用注解@JsonSerialize、@JsonDeserialize注册到Spring的ObjectMapper中
@JsonDeserialize(using = DescEnumDeserializer.class)@JsonSerialize(using = DescEnumSerializer.class)public interface I18NEnum { /** * 获取枚举形容 * * @return */ String getDesc();}
参考一下自定义的序列化实现:
/** * @author huangying */public class DescEnumSerializer extends JsonSerializer<I18NEnum> { @Override public void serialize(I18NEnum value, JsonGenerator gen, SerializerProvider serializers) throws IOException { // 按类名+枚举值名称拼接配置文件key,全副大写解决 String key = value.getClass().getSimpleName() + "." + StringUtils.upperCase(value.toString()); // I18NUtil为国际化解决工具类 String data = I18NUtil.get(key, value.getDesc()); gen.writeString(data); }}
自定义的反序列化实现:
/** * @author huangying */public class DescEnumDeserializer extends JsonDeserializer<I18NEnum> { @Override public I18NEnum deserialize(JsonParser p, DeserializationContext ctx) throws IOException { JsonNode node = p.getCodec().readTree(p); Class enumCls = BeanUtils.findPropertyType(p.currentName(), p.getCurrentValue().getClass()); List enumFields = EnumUtils.getEnumList(enumCls); String keyPrefix = enumCls.getSimpleName() + "."; for (Object enumField : enumFields) { I18NEnum i18NEnum = (I18NEnum) enumField; // I18NUtil为国际化解决工具类 String data = I18NUtil.get(keyPrefix + StringUtils.upperCase(i18NEnum.toString()), i18NEnum.getDesc()); if (node.asText().equals(data)) { return i18NEnum; } } throw new I18NEnumException("enum:未知的枚举类型"); }}
自定义一个专用异样,这样看起来更加高大上:
/** * @author huangying */public class I18NEnumException extends RuntimeException { public I18NEnumException(String message) { super(message); }}
国际化解决工具类
这个国际化解决的工具类是通用的,读取我的项目工程里的messages.propertiesmessages_zh_CN.propertiesmessages_en.properties等配置文件的MessageSource信息,并依据具体的语言,返回信息来实现国际化显示,代码如下:
/** * @author huangying */@Componentpublic class I18NUtil { private static MessageSource messageSource; public I18NUtil(MessageSource messageSource) { I18NUtil.messageSource = messageSource; } public static String get(String key) { return messageSource.getMessage(key, null, LocaleContextHolder.getLocale()); } public static String get(String key, Object arg) { return messageSource.getMessage(key, new Object[]{arg}, LocaleContextHolder.getLocale()); }}
Enum定义示例
咱们举一个enum定义的示例,有SUCCESS和FAIL两个枚举值,存储在数据库中的int值别离是1和2:
public enum OperateEnum implements I18NEnum { /** * 集体日常生产 */ SUCCESS(1, "SUCCESS"), /** * 装修 */ FAIL(2,"FAIL"); private int index; private String desc; OperateEnum(int index, String desc) { this.index = index; this.desc = desc; } @Override public String getDesc() { return desc; } public int getIndex() { return index; }}
配置文件的写法:
# messages.properties内容# 枚举类OperateEnum.SUCCESS=successOperateEnum.FAIL=fail# messages_zh_CN.properties内容# 枚举类OperateEnum.SUCCESS=操作胜利OperateEnum.FAIL=操作失败
计划利用
在SpringCloud环境下,增加对国际化语言的解决,咱们对立将国内语言标识放在request header的lang外面:
/** * @author huangying */public class I18NLocalResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest request) { String lang = request.getHeader("lang"); //获取jvm默认locale Locale locale = Locale.getDefault(); if (lang != null) { locale = new Locale(lang); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { }}
自定义enum的序列化办法触发
在接口里只须要将enum类返回,在@ResponseBody进行解决时即可触发enum国际化的序列化办法,示例接口如下:
@ApiOperation(value = "枚举值国际化示例")@ApiImplicitParams({ @ApiImplicitParam(name = "uid", value = "操作人员ID", paramType = "header", dataType = "Long")})@RequestMapping(value = "/test/enums", method = RequestMethod.GET)public Result get( @RequestHeader(value = "lang") String lang) { return Result.success(EnumUtils.getEnumList(OperateEnum.class));}
自定义enum的反序列化办法触发
MappingJackson2HttpMessageConverter转换器默认将@RequestBody的内容做反序列化解决,如果enum的国际化值传递给了客户端,若须要正确处理客户端提交的枚举值国际化内容,最简略的方法是将enum定义在@RequestBody的对象中,就能主动触发enum的自定义反序列化办法,并失去冀望的后果。
若在@RequestParam润饰的参数上定义enum对象,申请中的String转换成enum是通过org.springframework.core.convert.support.StringToEnumConverterFactory 来实现的,该类实现了接口 ConverterFactory ,通过调用 Enum.valueOf(Class, String) 实现了这个性能,而不会触发enum枚举值的反序列化。因而只能解决与枚举值雷同的字面值(name),enum枚举值国际化解决后,可能与字面值不雷同,间接应用@RequestParam来转换,会报错。
如果要让@RequestParam可能触发enum枚举值的反序列化操作,能够尝试重写springmvc的参数转换器,此处略。
小结
enum枚举值的国际化解决,是个十分有意思的改良,既可能解决浏览性的问题,又进步了业务定义的内聚性,此计划的利用取决于前后端的编码习惯,如果是在我的项目初期,前后端童鞋沟通确认后能够尝试此计划,心愿对你有帮忙。
专一Java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区
能够扫右边二维码增加好友,邀请你退出Java架构社区微信群独特探讨技术