zoukankan      html  css  js  c++  java
  • 基于SpringCloud的enum枚举值国际化处理实践

    背景

    选用SpringCloud框架搭建微服务做业务后台应用时,会涉及到大量的业务状态值定义,一般常规做法是:

    • 持久层(数据库)存储int类型的值
    • 后台系统里用阅读性好一点儿的常量将int类型的值做一层映射
    • 前端(app或浏览器)同样定义一套常量去映射这些关系
    • 前端调用后台系统的接口时,使用常量定义的int类型进行提交

    源于持久层存储的优化规则,int类型要比varchar类型效率高很多,这套做法也是大家接受度非常高的。

    只是这里有一个不是很方便的地方:状态值映射的常量定义涉及前端和后台两部分,沟通的成本是一方面,另外如果状态值有变化,需要两组人员同时修改。

    预期目标

    在保证持久层的int类型存储状态值的前提下,主要是考虑业务状态的可阅读性问题和多处修改的问题,可阅读性问题一部分可以通过前后端人员定义常量来解决,但接口调试时还是直接使用int类型,这部分的可阅读性问题还是存在,多处修改的问题需要重点解决。

    本篇推荐的方案:

    • 持久层(数据库)存储没用原先的int类型值,这点保持不变
    • 后台系统使用enum定义业务状态,不同的业务状态集可以由多个enum来实现,enum支持国际化
    • 前端展示enum国际化的文本内容
    • 前端调用后台系统接口时,使用enum国际化的文本内容进行提交
    • 后台接收enum国际化的文本内容转换成int类型值,存储在数据库

    方案的优点:

    • 持久层原有的设计,效率性问题不受影响
    • 业务状态的定义、映射全部内聚到后台系统,后续有状态值变化时,只需后台做相应修改即可
    • 前端展示的内容,接口传输的内容均为阅读性更好的文本,并且支持国际化

    方案的缺点:

    • 后台系统存储、读取状态值时,需要用enum进行转换
    • 通信传输的内容报文比原有的int类型大一点点

    方案实践

    实践原理

    此实践方案主要包含三部分:

    1. Enum类使用Jackson进行JSON序列化和反序列化
    2. Enum枚举项的messages国际化处理
    3. 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
     */
    @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;
    	}
    
    	@Override
    	public 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 {
    
        @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架构社区微信群共同探讨技术
    Java架构社区

  • 相关阅读:
    Android 数据存储之 SQLite数据库存储
    Android 数据存储之 SharedPreferences储存
    Android 数据存储之 文件存储
    SSTABLE简介
    zookeeper原理
    改变win7驱动图标
    也谈谈拖延癌
    STM32f103之外部中断
    LPC1768/1769之CAN控制器概述(附库函数下载地址)
    Silicon C8051F340之时钟系统
  • 原文地址:https://www.cnblogs.com/huangying2124/p/13412354.html
Copyright © 2011-2022 走看看