基于Jackson自定義BeanSerializerModifier序列化

解決問題:java返回對象中關(guān)于枚舉缠诅、字典數(shù)據(jù)的自動轉(zhuǎn)化哑蔫。
實現(xiàn)思路:
1钉寝、通過自定義注解 對需要轉(zhuǎn)化的字段進行標記,注解中可定義枚舉類型闸迷,若沒有定義枚舉則從數(shù)據(jù)字典獲取嵌纲。
2、自定義對象的BeanSerializerModifier腥沽,對做了標記的字段設(shè)置自定義的JsonSerializer逮走。
3、自定義JsonSerializer的實現(xiàn)今阳。
4师溅、自定義MappingJackson2HttpMessageConverter,并設(shè)置自定義的BeanSerializerModifier為默認處理方式盾舌。
5墓臭、將自定義的MappingJackson2HttpMessageConverter加入到HttpMessageConverters中,可以通過重寫WebMvcConfigurationSupport.extendMessageConverters的方式實現(xiàn)妖谴。
以下是具體的代碼實現(xiàn)窿锉,有些地方需要用戶根據(jù)實際情況自己實現(xiàn),比如從字典獲取數(shù)據(jù)等窖维。

自定義注解標記

import java.lang.annotation.*;

@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Dict {
    static enum Void {}

    Class<? extends Enum<?>> enumType() default Void.class;

    /**
     * 默認值榆综,獲取不到字典則使用默認值
     *
     * @return ""
     */
    String defaultValue() default "";
}

自定義BeanSerializerModifier,以下代碼中DictConstants.getDictCacheKey(valueStr)只是自定義key铸史,需要自己實現(xiàn)鼻疮。

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.hg.dcm.commons.core.HGBusinessException;
import com.hg.dcm.commons.core.SpringContextUtil;
import com.hg.energy.service.RedisService;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

@Slf4j
public class DictSerializerModifier extends BeanSerializerModifier {
    @Override
    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
        for (BeanPropertyWriter beanProperty : beanProperties) {
            Dict dict = beanProperty.getAnnotation(Dict.class);
            if (dict != null) {
                beanProperty.assignSerializer(new DictSerializer(dict));
            }
        }
        return beanProperties;
    }

    /**
     * 字典自定義序列化
     */
    static class DictSerializer extends JsonSerializer<Object> {
        /**
         * 生成序列化字段后綴
         */
        private static final String LABEL_SUFFIX = "Desc";
        /**
         * 字典配置信息
         */
        private final Dict dict;

        /**
         * 枚舉獲取key方法
         */
        private static final String[] KEY_METHODS = {"getValue", "getCode", "getStatus", "name"};
        /**
         * 獲取枚舉描述方法
         */
        private static final String[] DESC_METHODS = {"getDesc"};

        /**
         * 構(gòu)造方法
         *
         * @param dict 字典描述
         */
        public DictSerializer(Dict dict) {
            this.dict = dict;
        }

        /**
         * Method that can be called to ask implementation to serialize
         * values of type this serializer handles.
         *
         * @param value    Value to serialize; can <b>not</b> be null.
         * @param gen      Generator used to output resulting Json content
         * @param provider Provider that can be used to get serializers for
         *                 serializing Objects value contains, if any.
         */
        @Override
        public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            provider.defaultSerializeValue(value, gen);
            // 添加轉(zhuǎn)換之后的字段:xxxDesc
            String fieldName = gen.getOutputContext().getCurrentName();
            gen.writeStringField(fieldName.concat(LABEL_SUFFIX), value != null ? this.getDesc(dict, value) : null);
        }


        /**
         * 獲取字典信息
         * TODO
         *
         * @param dict  字典對象
         * @param value 字典值
         * @return
         */
        private String getDesc(Dict dict, Object value) {
            try {
                // 先查詢是否是枚舉類型,查到則返回
                String enumDesc = this.getEnumDesc(dict, value);
                if (enumDesc != null) {
                    return enumDesc;
                }
                String valueStr = Objects.toString(value);
                //獲取緩存key琳轿,可以自定義
                String key = DictConstants.getDictCacheKey(valueStr);
                // Redis 緩存操作類 這里建議優(yōu)先使用本地緩存, 本地緩存 -> redis -> Db
                RedisService redis = SpringContextUtil.getBean(RedisService.class);
                if (redis.exists(key)) {
                    return redis.get(key);
                }
                // 數(shù)據(jù)庫字典操作類

                //redis.setEx(key, desc, 1, TimeUnit.HOURS);
                return "字典數(shù)據(jù)";
            } catch (Exception e) {
                log.error("字典轉(zhuǎn)換:獲取字典描述異常,使用默認值:{}判沟,key:{}, dict:{}, 異常:{}", dict.defaultValue(), value, dict.enumType(), e.getMessage(), e);
                return dict.defaultValue();
            }
        }


        /**
         * 獲取枚舉類型的描述信息
         *
         * @param dict  字典
         * @param value 值
         * @return 枚舉desc字段
         */
        private String getEnumDesc(Dict dict, Object value) throws InvocationTargetException, IllegalAccessException {
            if (dict == null || value == null) {
                return null;
            }
            Class<? extends Enum<?>> et = dict.enumType();
            if (Dict.Void.class.equals(et)) {
                return null;
            }

            Enum<?>[] enums = et.getEnumConstants();
            Method keyMethod = this.getMethod(et, KEY_METHODS);
            if (keyMethod == null) {
                // 自定義業(yè)務(wù)異常
                throw new HGBusinessException(String.format("字典轉(zhuǎn)換:枚舉:%s,沒有方法:%s", et.getName(), Arrays.toString(KEY_METHODS)));
            }
            Method descMethod = this.getMethod(et, DESC_METHODS);
            if (descMethod == null) {
                throw new HGBusinessException(String.format("字典轉(zhuǎn)換:枚舉:%s,沒有方法:%s", et.getName(), Arrays.toString(DESC_METHODS)));
            }
            for (Enum<?> e : enums) {
                if (value.equals(keyMethod.invoke(e))) {
                    return Objects.toString(descMethod.invoke(e));
                }
            }
            log.error("字典轉(zhuǎn)換:通過枚舉轉(zhuǎn)換失敗耿芹,枚舉:{},值:{}挪哄,KeyMethod:{}吧秕,DescMethod:{}", et.getName(), value, Arrays.toString(KEY_METHODS), Arrays.toString(DESC_METHODS));
            throw new HGBusinessException(String.format("字典轉(zhuǎn)換失敗,枚舉:%s迹炼,值:%s", et.getName(), value));
        }

        /**
         * 獲取讀方法
         *
         * @param enumType    枚舉類
         * @param methodNames 方法名稱
         * @return Method
         */
        private Method getMethod(Class<? extends Enum<?>> enumType, String... methodNames) {
            for (String methodName : methodNames) {
                try {
                    return enumType.getMethod(methodName);
                } catch (NoSuchMethodException e) {
                }
            }
            return null;
        }
    }
}

自定義MappingJackson2HttpMessageConverter

    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        SimpleModule simpleModule = new SimpleModule().setSerializerModifier(new DictSerializerModifier());
        builder.modules(simpleModule);
        builder.serializationInclusion(JsonInclude.Include.NON_NULL);
        return new MappingJackson2HttpMessageConverter(builder.build());
    }

將自定義的MappingJackson2HttpMessageConverter加入到HttpMessageConverters中

@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {


    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(0,mappingJackson2HttpMessageConverter());
    }


    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        SimpleModule simpleModule = new SimpleModule().setSerializerModifier(new DictSerializerModifier());
        builder.modules(simpleModule);
        builder.serializationInclusion(JsonInclude.Include.NON_NULL);
        return new MappingJackson2HttpMessageConverter(builder.build());
    }

}

參考文獻:http://www.reibang.com/p/90d7d101e715

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末砸彬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子斯入,更是在濱河造成了極大的恐慌砂碉,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刻两,死亡現(xiàn)場離奇詭異增蹭,居然都是意外死亡,警方通過查閱死者的電腦和手機磅摹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門滋迈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人户誓,你說我怎么就攤上這事饼灿。” “怎么了厅克?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵赔退,是天一觀的道長。 經(jīng)常有香客問我证舟,道長,這世上最難降的妖魔是什么窗骑? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任女责,我火速辦了婚禮,結(jié)果婚禮上创译,老公的妹妹穿的比我還像新娘抵知。我一直安慰自己,他們只是感情好软族,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布刷喜。 她就那樣靜靜地躺著,像睡著了一般立砸。 火紅的嫁衣襯著肌膚如雪掖疮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天颗祝,我揣著相機與錄音浊闪,去河邊找鬼恼布。 笑死,一個胖子當著我的面吹牛搁宾,可吹牛的內(nèi)容都是我干的折汞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盖腿,長吁一口氣:“原來是場噩夢啊……” “哼爽待!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起翩腐,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤堕伪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后栗菜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欠雌,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年疙筹,在試婚紗的時候發(fā)現(xiàn)自己被綠了富俄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡而咆,死狀恐怖霍比,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情暴备,我是刑警寧澤悠瞬,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站涯捻,受9級特大地震影響浅妆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜障癌,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一凌外、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涛浙,春花似錦康辑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至我注,卻和暖如春按咒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仓手。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工胖齐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玻淑,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓呀伙,卻偏偏與公主長得像补履,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剿另,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容