java 自定义注解 生成json_用自定义注解实现fastjson序列化的扩展
這篇文章起源于項(xiàng)目中一個(gè)特殊的需求。由于目前的開(kāi)發(fā)方式是前后端分離的,基本上是通過(guò)接口提供各個(gè)服務(wù)。
而前兩天前端fe在開(kāi)發(fā)中遇到了一些問(wèn)題:他們?cè)谔幚碜址?lèi)型的時(shí)間時(shí)會(huì)出現(xiàn)精度丟失的情況,所以希望后臺(tái)是以時(shí)間戳的形式返回給前端。而與此同時(shí)后臺(tái)的設(shè)計(jì)是這個(gè)樣子的:所有的時(shí)間在數(shù)據(jù)庫(kù)中均保存為varchar類(lèi)型,在序列化的時(shí)候也是按String字符串去處理的。
這樣一來(lái)就需要一些解決方案:
1. 所有數(shù)據(jù)庫(kù)的時(shí)間字段都用timestamp替換,這個(gè)是最簡(jiǎn)單確實(shí)實(shí)現(xiàn)代價(jià)最高的一種方案,由于數(shù)據(jù)庫(kù)表太多并且涉及多處的耦合,此方案不可行。
2. 通過(guò)fastjson序列化層去轉(zhuǎn)換類(lèi)型:首先想到的是能不能通過(guò)fastjson自己提供的注解方式實(shí)現(xiàn),后調(diào)研之后發(fā)現(xiàn),目前使用的版本并不支持;然后想到可以通過(guò)fastjson提供的序列化器重載實(shí)現(xiàn)String類(lèi)型的攔截并做處理。但是這種方案會(huì)把所有String的字段都執(zhí)行這段邏輯,并且還要通過(guò)固定的format確定哪些是日期,這里非常影響性能;最后決定添加自定義注解,在需要這種類(lèi)型轉(zhuǎn)換的字段上加上自定義的@StringToDate注解,然后在序列化執(zhí)行的入口,給所有加了此注解的類(lèi)提供繼承并實(shí)現(xiàn)擴(kuò)展的序列化器,完成自定義的序列化過(guò)程。
下面就詳細(xì)說(shuō)一下這個(gè)過(guò)程,一是對(duì)fastjson源碼做一個(gè)解析和說(shuō)明,二也是對(duì)自己的工作做一個(gè)總結(jié)。
1. 如何實(shí)現(xiàn)擴(kuò)展
首先來(lái)看一下自定義的注解,很簡(jiǎn)單:
關(guān)于這個(gè)注解的用法和定義,我就不細(xì)說(shuō)了,Retention指定了他的作用域,而Target說(shuō)明該注解用于field字段上。
然后既然是要擴(kuò)展fastjson的功能,我們就來(lái)看一下fastjson的入口,首先是WebMvcConfigurerAdapter這個(gè)類(lèi),我們可以通過(guò)繼承和重寫(xiě)該類(lèi)的方法實(shí)現(xiàn)MVC的配置,比如攔截器、資源處理器等。我們重寫(xiě)的方法是configureMessageConverters,這個(gè)是用來(lái)配置信息轉(zhuǎn)化的converter:
可以看到,我們通過(guò)這個(gè)方法,調(diào)用了父類(lèi),同時(shí)加入了fastJson的converter,然后我們來(lái)看看FastJsonMessageConverter這個(gè)我們自己寫(xiě)的類(lèi):
只是很簡(jiǎn)單的通過(guò)繼承實(shí)現(xiàn)了自定義config的注入,然后再來(lái)看看我們自己寫(xiě)的這個(gè)config:
這里是最關(guān)鍵的地方,SerializeConfig提供一個(gè)入口方法getObjectWriter可以用來(lái)對(duì)傳入的類(lèi)型進(jìn)行處理,并為相應(yīng)的類(lèi)型設(shè)置對(duì)應(yīng)的Serializer序列化器。這里可以看到,我通過(guò)判斷傳入class的注解判斷是不是要處理的類(lèi)型,如果是需要轉(zhuǎn)換的,就用我們寫(xiě)的ExtendJavaBeanSerializer去處理他。(同時(shí)可以看到,這里我做了緩存處理,每次的class在處理之后都會(huì)通過(guò)父類(lèi)的put方法放入緩存,這樣可以大大減少遍歷和判斷的次數(shù),提高處理性能)
那現(xiàn)在就要看看這個(gè)ExtendJavaBeanSerializer做了什么:
這里我們繼承了JavaBeanSerializer并重寫(xiě)了processValue方法。這里要說(shuō)個(gè)點(diǎn):因?yàn)槊總€(gè)序列化器都有write方法,所以最開(kāi)始直觀的想法是重寫(xiě)這個(gè)方法實(shí)現(xiàn)擴(kuò)展,但是由于write方法很長(zhǎng)很長(zhǎng)(有250多行),作為切入點(diǎn)非常不方便,然后仔細(xì)觀察源碼,發(fā)現(xiàn)其中有這么一句:
propertyValue = this.processValue(serializer, fieldSerializer.fieldContext, object, fieldInfoName,propertyValue);
這句看上去好像就是給我們處理value值的,再點(diǎn)進(jìn)去一看,processValue居然是個(gè)protected方法。那正如意!這就是給我們做擴(kuò)展的入口,最后便重寫(xiě)了這個(gè)方法,并在其中實(shí)現(xiàn)了StringToDate的轉(zhuǎn)換邏輯。
2. fastjson源碼思想
其實(shí)呢,到這里應(yīng)該會(huì)有一個(gè)疑問(wèn),fastjson提供的預(yù)設(shè)序列化器有很多很多,看源碼發(fā)現(xiàn)一個(gè)包下面滿滿都是。那么我們是如何確定要繼承這個(gè)JavaBeanSerializer的呢?這就需要看fastjson的源碼了,看看它究竟是怎么分配和執(zhí)行這個(gè)序列化器的:
首先是SerializeConfig這個(gè)類(lèi):這個(gè)類(lèi)的config中預(yù)設(shè)了大量的類(lèi)型和序列化器的對(duì)應(yīng)關(guān)系,他原有的getObjectWriter入口是這樣的邏輯: 先判斷傳入類(lèi)是不是config中有的,如果有直接給對(duì)應(yīng)的Serializer,如果沒(méi)有,又會(huì)有一堆特殊類(lèi)(比如集合,異常,編碼等類(lèi))的判斷,而他們也有對(duì)應(yīng)的Serializer。
那如果還沒(méi)有(比如自己的javabean),在最后會(huì)執(zhí)行一個(gè):createJavaBeanSerializer(clazz) 的方法,在這個(gè)方法中,默認(rèn)會(huì)執(zhí)行createASMSerializer方法,asm查閱資料會(huì)發(fā)現(xiàn)是一個(gè)老外寫(xiě)的字節(jié)碼序列化器,是序列化最底層的一個(gè)工具,很多開(kāi)源序列化包都是對(duì)這個(gè)進(jìn)行封裝實(shí)現(xiàn)的。那我們要繼承JavaBeanSerializer這個(gè)類(lèi)并使用自己的,就說(shuō)明我們不想讓他執(zhí)行createASMSerializer這個(gè)方法,為什么呢?
因?yàn)樵赾reateASMSerializer里面的邏輯是:當(dāng)序列化對(duì)象 > 256個(gè)時(shí),會(huì)創(chuàng)建可繼承的JavaBeanSerializer去處理,如果<256,則不會(huì)給他任何序列化器,而是直接通過(guò)反射方式在內(nèi)存中(就是生生拼出了一個(gè)序列化過(guò)程)實(shí)現(xiàn)對(duì)這個(gè)對(duì)象的處理。關(guān)鍵代碼如下:
當(dāng)然,下面還有很長(zhǎng)很長(zhǎng)的內(nèi)存字節(jié)碼操作的邏輯,我就不貼出了。我想這可能也是fastjson快的一個(gè)原因吧。
寫(xiě)到這里,關(guān)于對(duì)fastjson的擴(kuò)展就差不多說(shuō)完了,其實(shí)fastjson的源碼中其他類(lèi)中也都有自己獨(dú)特的優(yōu)化方式,有些還是挺有意思的。另外呢,我驚奇地發(fā)現(xiàn),fastjson的作者和durid居然是一個(gè)人,這個(gè)來(lái)自阿里的wenshao還真的厲害啊。作為一個(gè)學(xué)習(xí)者,只是希望有朝一日也能寫(xiě)出這么漂亮的代碼吧,希望自己可以不斷努力!
總結(jié)
以上是生活随笔為你收集整理的java 自定义注解 生成json_用自定义注解实现fastjson序列化的扩展的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: vue 日期格式化返回指定个数月份_12
- 下一篇: oracle1461,Oracle 10