基于SpringCloud的enum枚举值国际化处理实践
背景
选用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> {@Overridepublic 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> {@Overridepublic 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.properties\messages_zh_CN.properties\messages_en.properties等配置文件的MessageSource信息,并根据具体的语言,返回信息来完成国际化显示,代码如下:
/*** @author huangying*/
@Component
public 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;}@Overridepublic String getDesc() {return desc;}public int getIndex() {return index;}
}
配置文件的写法:
# messages.properties内容
# 枚举类
OperateEnum.SUCCESS=success
OperateEnum.FAIL=fail# messages_zh_CN.properties内容
# 枚举类
OperateEnum.SUCCESS=操作成功
OperateEnum.FAIL=操作失败
方案应用
在SpringCloud环境下,添加对国际化语言的处理,我们统一将国际语言标识放在request header的lang里面:
/*** @author huangying*/
public class I18NLocalResolver implements LocaleResolver {@Overridepublic Locale resolveLocale(HttpServletRequest request) {String lang = request.getHeader("lang");//获取jvm默认localeLocale locale = Locale.getDefault();if (lang != null) {locale = new Locale(lang);}return locale;}@Overridepublic 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架构社区微信群共同探讨技术
![Java架构社区](https://img-blog.csdnimg.cn/20200118091355702.jpg?x-oss-
process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2h1YW5neWluZzIxMjQ=,size_16,color_FFFFFF,t_70)
基于SpringCloud的enum枚举值国际化处理实践相关推荐
- java enum枚举类的用法以及高级玩法
enum(枚举)类介绍 java枚举类是一组预定义常量的集合,使用enum关键字声明这个类,常量名称官方建议大写 1.enum类基本用法 举一个常见的例子,例如星期就可以描述为一个枚举类,如下 pub ...
- ios 获取一个枚举的所有值_Java enum枚举在实际项目中的常用方法
在项目实际开发过程中,经常会遇到对某些固定的值.字典项的定义的需求,很多项目经常使用常量来定义,其实在jdk1.5就已经引入了枚举,使用枚举可以更好的解决这类需求,本文主要记录枚举的优势以及经常在项 ...
- mysql characterencoding 枚举值_mysql中enum类型理解,读完之后,大部分程序员收藏了......
ENUM是枚举类型,它虽然只能保存一个值,却能够处理多达65535个预定义的值.下面是我写的一个mysql语句 CREATE TABLE student(id INT(11) PRIMARY key ...
- 取枚举的参数c语言,求解:如何获得enum类型中枚举值的数量
评论 # re: 求解:如何获得enum类型中枚举值的数量 2009-11-11 20:43 OwnWaterloo # re: 求解:如何获得enum类型中枚举值的数量[未登录] 2009-11-1 ...
- java枚举值转化中文_多个枚举的国际化(枚举值的转换)
好了,现在这是完整的和随时使用的解决方案:(感谢@Joop埃根) 使类 public final class EnumTranslator { public static String getMess ...
- Java 获取Enum枚举中的值,以列表方式返回
有时候,有一些下拉选择器的数据项,我们采取了枚举的方式简单记录,但是里面的东西多,前端不想写死,需要提供接口返回. 示例: 枚举 /*** @Author: JCccc* @Description:* ...
- 基于SpringCloud的广告系统设计与实现
基于SpringCloud的广告系统设计与实现 第二章 广告系统概览与准备工作 第三章 广告系统骨架开发 maven基础 构建工程基本骨架 建立工程 建立ad-eureka 微服务架构 新建ad-ga ...
- 如何从Java中的字符串值获取枚举值?
说我有一个枚举 public enum Blah {A, B, C, D } 我想找到一个字符串的枚举值,例如"A"就是Blah.A 怎么可能做到这一点? Enum.valueOf ...
- 将枚举值转换为DropDownList等的选项值
应用场景:在项目中,我们已习惯将较稳定的分类标准定义为枚举,来保证程序中取值的合法性,也使代码更清晰.某些情况下我们需要在DropDownList等选择控件上绑定某个枚举的所有枚举值,供用户自行选择, ...
- springboot 定时器_基于SpringCloud?+?SpringBoot的 SaaS型微服务脚手架源码分享
简介: 基于SpringCloud(Hoxton.SR3) + SpringBoot(2.2.6.RELEASE) 的 SaaS型微服务脚手架,具备用户管理.资源权限管理.网关统一鉴权.Xss防跨站攻 ...
最新文章
- spring data redis 使用之 spring boot 2.x
- ios中的视频采集及参数设置和相机操作
- 让struts2和servlet共存
- SQL Server 中索引的禁用与删除
- wan口有流量但电脑上不了网_wan口有ip地址但是上不了网怎么办?
- ASP.Net Jquery 随机验证码 文本框判断
- SSM-水果商城-项目展示-学习-1
- FFmpeg的编解码(二)
- 在Centos7上安装vpnc客户端
- 磁盘列阵 Raid
- 码出高效:Java开发手册PDF
- 基于事件触发的二阶多智能体领导跟随一致性
- 数据库驱动和maven
- sql server(MsSql)字段命名,表命名,视图命名,SQL语句书写参考
- 电脑录屏没有声音该怎么办
- mysql数据库全局搜索_数据库全局搜索
- BarChart 柱状图 实例
- 发邮件的JAVA程序
- domoticz用手机摄像头实现家庭监控
- 正确思维,和非理性自我斗争