美文网首页
关于项目中的枚举类型使用的规范化

关于项目中的枚举类型使用的规范化

作者: 乐不思孰 | 来源:发表于2018-03-29 19:48 被阅读0次

    问题背景

    公司中对枚举的使用规范(姑且称为规范吧)是对枚举定义一个int类型的code成员变量,同时定义配套的code()和codeOf(int)方法,枚举的存储、传输都是用code来代表具体的枚举值;
    问题在于,int类型虽然便于存储和传输,但是int类型无法体现不同枚举类型的信息,无法充分利用特殊化的类型信息进行编程,在代码中直接用int来定义方法参数、处理业务逻辑也会给人带来模糊的含义(往往只能靠字段名来识别int类型的含义)。
    总的来说,除了传输和存储以外,应用代码中应该统一使用纯的枚举类型而不是int,而应用代码与外部的交互则是使用经过转换的int。这里需要解决问题的便是应用边界处的枚举转换问题。

    最佳实践

    • Dao层

      使用公司common包中既有的CodeEnumTypeHandler,由于涉及到从int到特定类型枚举的反向转换,该TypeHandler中需要保存特定枚举的class信息;使用的时候需要给CodeEnumTypeHandler配置对应的枚举类路径作为构造参数。

        <!-- 在mybatis-config.xml配置CodeEnumTypeHandler -->
        <typeHandlers>
            <!-- 为每个枚举类注册一个CodeEnumTypeHandler -->
            <typeHandler javaType="com.qunar.flight.jy.api.enums.BusinessScope"
                         handler="com.qunar.base.meerkat.orm.mybatis.type.CodeEnumTypeHandler"/>
            <typeHandler ... />
            <package name="com.qunar.flight.jy.common.utils.database.handler"/>
        </typeHandlers>
      

      // 当前的使用方式需要我们为枚举类逐个配置CodeEnumTypeHandler,考虑如何实现CodeEnumTypeHandler的自动化注册

    • Service层

      业务层,当需要将qconfig的json配置或者http接口的json返回值解析为包含枚举类型的对象时,需要使用定制化的ObjectMapper实例。
      在此之前,我们需要一个CodeEnumJsonSerialzer和一个CodeEnumJsonDeserialzer。

        /**
         * Code枚举json序列化
         */
        public class CodeEnumJsonSerializer<T extends Enum<T>> extends JsonSerializer<T> {
         
            @Override
            public void serialize(T value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                int code = CodeEnumUtil.code(value);
                gen.writeNumber(code);
            }
         
        }
        
        /**
         * Code枚举json反序列化
         */
        public class CodeEnumJsonDeserializer<T extends Enum<T>> extends JsonDeserializer<T> implements ContextualDeserializer {
         
            @SuppressWarnings("unchecked")
            @Override
            public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
                Class<T> enumType = (Class<T>) ctxt.getAttribute(p.getCurrentName());
                return CodeEnumUtil.codeOf(enumType, p.getValueAsInt());
            }
         
            @Override
            public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
                    throws JsonMappingException {
                ctxt.setAttribute(property.getName(), property.getType().getRawClass());
                return this;
            }
        }
      

      // 实际使用中, 发现通用的反序列化不一定生效(待解决); 这种情况下, 一个退而求其次的办法是给枚举字段加上@JsonDeserialize注解, 每个字段使用指定的反序列化类

      为ObjectMapper注册一个支持CodeEnum枚举序列化和解序列化的module,并将ObjectMapper声明为spring bean:

        /**
         * ObjectMapper配置类, 对枚举类型使用code值做json序列化和解序列化的ObjectMapper
         *
         * @author chenjiahua.chen
         */
        @Configuration
        public class ObjectMapperConfiguration {
         
            @Bean
            public ObjectMapper objectMapper() {
                SimpleModule module = new SimpleModule();
                module.addSerializer(Enum.class, new CodeEnumJsonSerializer<>());
                module.addDeserializer(Enum.class, new CodeEnumJsonDeserializer<>());
                // TO.DO. 更多规范化的ser和deser, 如DateTime类型
                // TO.DO. 更加合理化的ObjectMapper序列化、解系列化配置,如忽略null字段
                return new ObjectMapper().registerModule(module);
            }
        }
      

      在相关的service类中注入配置过的objectMapper:

        @Component
        public class AccountingItemConfig {
         
            @Resource
            private ObjectMapper objectMapper;
         
            // use objectMapper to do something
        }
      

      // 对于dubbo接口,最好在接口定义阶段就使用枚举,提取出枚举的api(但要考虑api升级的兼容性问题?!)

    • Controller层

      Controller的处理方式取决于前端参数的提交方式和后端数据的返回方式。

      1)使用表单默认的数据编码方式(content-type为application/x-www-form-urlencoded类型),对参数的转换需要使用表单参数绑定方法(@InitBinder方法),可以在@ControllerAdvice注解的类中定义全局的@InitBinder方法:

        /**
         * 全局参数绑定的@ControllerAdvice类
         *
         * @author chenjiahua.chen
         */
        @ControllerAdvice
        public class GlobalControllerInitBinder {
         
            @InitBinder
            public void initBinderBusinessScope(WebDataBinder binder) {
                registerEnumEditor(binder, ProfitType.class, "profitType");
                registerEnumEditor(binder, BusinessScope.class, "businessScope");   // 可以不指定字段名
                // 其他类型参数绑定...
            }
         
            private <T extends Enum<T>> void registerEnumEditor(WebDataBinder binder, Class<T> clz, String propertyName) {
                binder.registerCustomEditor(clz, propertyName, newEnumEditor(clz));
            }
         
            private <T extends Enum<T>> PropertyEditor newEnumEditor(Class<T> clz) {
                return new PropertyEditorSupport() {
                    @Override
                    public void setAsText(String text) {
                        setValue(CodeEnumUtil.codeOf(clz, Integer.parseInt(text)));
                    }
                };
            }
        }
      

      2)使用json的编码方式(content-type为application/json类型),需要定制或者扩展Spring MVC的json消息转换器

        /**
         * 支持数字和枚举类型转换的json消息转换器
         *
         * @author chenjiahua.chen
         */
        public class CodeEnumJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
         
            public CodeEnumJackson2HttpMessageConverter() {
                super();
                registerModule();
            }
         
            private void registerModule() {
                SimpleModule module = new SimpleModule();
                module.addSerializer(Enum.class, new CodeEnumJsonSerializer());
                module.addDeserializer(Enum.class, new CodeEnumJsonDeserializer<>());
                objectMapper.registerModule(module);
            }
        }
      

      在mvc上下文中配置如下:

        <!-- 配置自定义的message-converter -->
        <mvc:annotation-driven>
            <mvc:message-converters>
                <bean class="com.qunar.flight.jy.common.utils.web.CodeEnumJackson2HttpMessageConverter"/>
            </mvc:message-converters>
        </mvc:annotation-driven>
      
    • 其他

      用到的枚举工具类:

        /**
         * 枚举类工具, 用于enum和int/String转换; 相关的枚举类应符合CodeEnum规范(包含成员方法code()和静态方法codeOf(int))
         *
         * @author chenjiahua.chen
         */
        public class CodeEnumUtil {
         
            private CodeEnumUtil() {
            }
         
            public static <T extends Enum<T>> int code(T t) {
                try {
                    Method code = t.getClass().getDeclaredMethod("code");
                    Object rs = code.invoke(t);
                    return (int) rs;
                } catch (ReflectiveOperationException e) {
                    throw new IllegalArgumentException(e);
                }
            }
         
            @SuppressWarnings("unchecked")
            public static <T extends Enum<T>> T codeOf(Class<T> enumType, int code) {
                try {
                    Method codeOf = enumType.getDeclaredMethod("codeOf", int.class);
                    Object rs = codeOf.invoke(null, code);
                    return (T) rs;
                } catch (ReflectiveOperationException e) {
                    throw new IllegalArgumentException(e);
                }
            }
         
            public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
                return T.valueOf(enumType, name);
            }
        }
      

      对于spring-boot应用,可以利用spring-boot提供的JsonComponentModule来扫描被@JsonComponent注解的类并自动注册JsonSerializer和JsonDeserializer。
      一个定义好的JsonComponent如下:

        /**
         * 该类结合spring-boot的{@link JsonComponentModule}使用, 可被自动发现并注册json序列化和反序列化组件
         *
         * @author chenjiahua.chen
         */
        @JsonComponent
        public class CodeEnumJsonComponent {
         
            private CodeEnumJsonComponent() {
            }
         
            public static class Ser extends CodeEnumJsonSerializer {
            }
         
            public static class Deser extends CodeEnumJsonDeserializer {
            }
        }

    相关文章

      网友评论

          本文标题:关于项目中的枚举类型使用的规范化

          本文链接:https://www.haomeiwen.com/subject/qmkvcftx.html