根據上一篇文章在springboot程序中jackson自定義註解和字段解析器的經驗,一開始的操作步驟如下
序列化的時候繼承了StdSerializer,本來想繼承StdDeserializer,但是它有個構造參數必須指定 com.fasterxml.jackson.databind.deser.std.StdDeserializer#StdDeserializer(com.fasterxml.jackson.databind.JavaType) protected StdDeserializer(JavaType valueType) { // 26-Sep-2017, tatu: [databind#1764] need to add null-check back until 3.x _valueClass = (valueType == null) ? Object.class : valueType.getRawClass(); _valueType = valueType; } 沒弄明白爲什麼要指定這個valueType,而且要放到構造方法,所以我直接繼承了JsonDeserializer,根據DeserializationContext對象也可以直接拿到JavaType呀,我可真是個大聰明~ @Slf4j @AllArgsConstructor @NoArgsConstructor public class HdxAesDataDeserializer extends JsonDeserializer<Object> { @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { String valueAsString = p.getValueAsString(); String s = HdxAesUtil.decryptHex(valueAsString); return ObjectMapperFactory.getObjectMapper().readValue(s, ctxt.getContextualType()); } } 2、定義反序列化自定義註解 這個註解是加到字段上的,但是之前的一篇文章 spring mvc請求體偷樑換柱:HandlerMethodArgumentResolver 這個註解已經加到了請求參數上,所以再添加一個允許加註解到字段即可 3、對註解註釋的字段反序列化支持 4、註冊到ObjectMapper 這段代碼和原先是一樣的 /** * @author kdyzm * @date 2021/10/27 */ @Configuration public class JsonConfig { /** * @param builder * @return * @link {https://stackoverflow.com/questions/34965201/customize-jackson-objectmapper-to-read-custom-annotation-and-mask-fields-annotat} * @see JacksonAutoConfiguration.JacksonObjectMapperConfiguration#jacksonObjectMapper(Jackson2ObjectMapperBuilder) */ @Bean @Primary ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper mapper = builder.createXmlMapper(false).build(); AnnotationIntrospector sis = mapper.getSerializationConfig().getAnnotationIntrospector(); AnnotationIntrospector is1 = AnnotationIntrospectorPair.pair(sis, new HdxAesDataAnnotationIntrospector()); mapper.setAnnotationIntrospector(is1); return mapper; } } 5、測試和新問題 上述步驟不多,但是似乎已經天衣無縫,信誓旦旦的來測試個 然後順利得到了一個空指針異常 最後debug得到的出問題的代碼在這裏,ctxt.getContextualType()獲取到的JavaType是空值。。 二、問題排查和解決方案 谷歌查了下,看到了有價值的github issue:Give Custom Deserializers access to the resolved target Class of the currently deserialized object 還有stackoverflow上的討論:How to create a general JsonDeserializer 這一切都指向了唯一一種解決方案:實現 ContextualDeserializer 接口,照葫蘆畫瓢,那就試試,改造後的代碼如下 /** * @author kdyzm * @date 2021/11/18 */ @Slf4j @AllArgsConstructor @NoArgsConstructor public class HdxAesDataDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer { private JavaType type; @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { String valueAsString = p.getValueAsString(); String s = HdxAesUtil.decryptHex(valueAsString); return ObjectMapperFactory.getObjectMapper().readValue(s, type); } @Override public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty beanProperty) throws JsonMappingException { //beanProperty is null when the type to deserialize is the top-level type or a generic type, not a type of a bean property JavaType type = deserializationContext.getContextualType() != null ? deserializationContext.getContextualType() : beanProperty.getMember().getType(); return new HdxAesDataDeserializer(type); } } 其實改完之後我是蒙圈的,我有幾點疑問 我不明白爲什麼實現了ContextualDeserializer接口之後實現的方法createContextual要返回一個新的JsonDeserializer對象,這個對象用在什麼地方的,和當前的this對象有什麼區別,如果是這麼搞,豈不是HdxAesDataDeserializer對象創建HdxAesDataDeserializer對象。。。擱這裏套娃呢? 這麼搞的話,需要引入一個成員變量type,在多線程環境下會不會因此出現線程安全性問題?很明顯,如果多線程共享HdxAesDataDeserializer對象,就會出現線程安全性問題,如果每次都新創建HdxAesDataDeserializer對象,就沒有線程安全性問題了。 總之是騾子是馬,拉出來溜溜,這麼一改,果然就好用了,但是用起來不痛快,畢竟還存在着疑問呢,帶着疑惑,我進行了源碼追蹤。 三、源碼追蹤和解惑 在相關的代碼打上斷點 然後運行測試代碼 1、最先運行無參構造方法 com.fasterxml.jackson.databind.util.ClassUtil#createInstance 這段代碼使用反射技術利用無參構造方法創建了HdxAesDataDeserializer對象。那麼調用時機如何呢,根據調用鏈繼續追蹤,可以看到調用點最終在這裏 這段代碼會單獨處理對象的每個成員變量的反序列化,然後每次都會在com.fasterxml.jackson.databind.deser.BeanDeserializerFactory#constructSettableProperty方法中尋找合適的反序列化工具 如果沒找到,則創建合適的反序列化工具 這說明了一個問題,每個成員變量在反序列化的時候如果是自定義的註解和反序列化類,每次都會新建反序列化類,也就不存在線程安全性問題了。 2、createContextual方法被調用 追查調用鏈,還是在com.fasterxml.jackson.databind.deser.BeanDeserializerFactory#constructSettableProperty方法中被調用的,這和上一步創建HdxAesDataDeserializer對象是同一個方法,也就是中1標誌的位置,2處標誌的位置則是現在createContextual方法被調用的位置。 可以看到,在調用默認構造方法創建了HdxAesDataDeserializer對象之後,又調用了一次createContextual方法使用帶參數的構造方法創建了HdxAesDataDeserializer對象並替換了老的deser對象。 到這裏就明白了,原來createContextual方法返回新的JsonSerilizer對象是爲了替換掉老的對象。 3、deserialize方法最後被調用 這時候使用的deser對象已經是createContextual返回的對象了,就可以正常使用JavaType進行反序列化了。 四、總結 1、反序列化關鍵點 最重要的是反序列化工具要繼承 JsonDeserializer並且實現ContextualDeserializer接口,實現ContextualDeserializer接口實現的createContextual接口會創建新的 JsonDeserializer對象並且替換掉當前的this對象。 2、線程安全性問題 由於引入了額外的JavaType成員變量,可能會存在線程安全性問題,但是通過源碼可以得知,針對每個成員變量,如果默認的不支持,則會創建相應的單獨的序列化工具,也就不存在線程安全性問題了。
com.fasterxml.jackson.databind.deser.std.StdDeserializer#StdDeserializer(com.fasterxml.jackson.databind.JavaType)
protected StdDeserializer(JavaType valueType) { // 26-Sep-2017, tatu: [databind#1764] need to add null-check back until 3.x _valueClass = (valueType == null) ? Object.class : valueType.getRawClass(); _valueType = valueType; }
沒弄明白爲什麼要指定這個valueType,而且要放到構造方法,所以我直接繼承了JsonDeserializer,根據DeserializationContext對象也可以直接拿到JavaType呀,我可真是個大聰明~ @Slf4j @AllArgsConstructor @NoArgsConstructor public class HdxAesDataDeserializer extends JsonDeserializer<Object> { @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { String valueAsString = p.getValueAsString(); String s = HdxAesUtil.decryptHex(valueAsString); return ObjectMapperFactory.getObjectMapper().readValue(s, ctxt.getContextualType()); } } 2、定義反序列化自定義註解 這個註解是加到字段上的,但是之前的一篇文章 spring mvc請求體偷樑換柱:HandlerMethodArgumentResolver 這個註解已經加到了請求參數上,所以再添加一個允許加註解到字段即可 3、對註解註釋的字段反序列化支持 4、註冊到ObjectMapper 這段代碼和原先是一樣的 /** * @author kdyzm * @date 2021/10/27 */ @Configuration public class JsonConfig { /** * @param builder * @return * @link {https://stackoverflow.com/questions/34965201/customize-jackson-objectmapper-to-read-custom-annotation-and-mask-fields-annotat} * @see JacksonAutoConfiguration.JacksonObjectMapperConfiguration#jacksonObjectMapper(Jackson2ObjectMapperBuilder) */ @Bean @Primary ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper mapper = builder.createXmlMapper(false).build(); AnnotationIntrospector sis = mapper.getSerializationConfig().getAnnotationIntrospector(); AnnotationIntrospector is1 = AnnotationIntrospectorPair.pair(sis, new HdxAesDataAnnotationIntrospector()); mapper.setAnnotationIntrospector(is1); return mapper; } } 5、測試和新問題 上述步驟不多,但是似乎已經天衣無縫,信誓旦旦的來測試個 然後順利得到了一個空指針異常 最後debug得到的出問題的代碼在這裏,ctxt.getContextualType()獲取到的JavaType是空值。。 二、問題排查和解決方案 谷歌查了下,看到了有價值的github issue:Give Custom Deserializers access to the resolved target Class of the currently deserialized object 還有stackoverflow上的討論:How to create a general JsonDeserializer 這一切都指向了唯一一種解決方案:實現 ContextualDeserializer 接口,照葫蘆畫瓢,那就試試,改造後的代碼如下 /** * @author kdyzm * @date 2021/11/18 */ @Slf4j @AllArgsConstructor @NoArgsConstructor public class HdxAesDataDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer { private JavaType type; @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { String valueAsString = p.getValueAsString(); String s = HdxAesUtil.decryptHex(valueAsString); return ObjectMapperFactory.getObjectMapper().readValue(s, type); } @Override public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty beanProperty) throws JsonMappingException { //beanProperty is null when the type to deserialize is the top-level type or a generic type, not a type of a bean property JavaType type = deserializationContext.getContextualType() != null ? deserializationContext.getContextualType() : beanProperty.getMember().getType(); return new HdxAesDataDeserializer(type); } } 其實改完之後我是蒙圈的,我有幾點疑問 我不明白爲什麼實現了ContextualDeserializer接口之後實現的方法createContextual要返回一個新的JsonDeserializer對象,這個對象用在什麼地方的,和當前的this對象有什麼區別,如果是這麼搞,豈不是HdxAesDataDeserializer對象創建HdxAesDataDeserializer對象。。。擱這裏套娃呢? 這麼搞的話,需要引入一個成員變量type,在多線程環境下會不會因此出現線程安全性問題?很明顯,如果多線程共享HdxAesDataDeserializer對象,就會出現線程安全性問題,如果每次都新創建HdxAesDataDeserializer對象,就沒有線程安全性問題了。 總之是騾子是馬,拉出來溜溜,這麼一改,果然就好用了,但是用起來不痛快,畢竟還存在着疑問呢,帶着疑惑,我進行了源碼追蹤。 三、源碼追蹤和解惑 在相關的代碼打上斷點 然後運行測試代碼 1、最先運行無參構造方法 com.fasterxml.jackson.databind.util.ClassUtil#createInstance 這段代碼使用反射技術利用無參構造方法創建了HdxAesDataDeserializer對象。那麼調用時機如何呢,根據調用鏈繼續追蹤,可以看到調用點最終在這裏 這段代碼會單獨處理對象的每個成員變量的反序列化,然後每次都會在com.fasterxml.jackson.databind.deser.BeanDeserializerFactory#constructSettableProperty方法中尋找合適的反序列化工具 如果沒找到,則創建合適的反序列化工具 這說明了一個問題,每個成員變量在反序列化的時候如果是自定義的註解和反序列化類,每次都會新建反序列化類,也就不存在線程安全性問題了。 2、createContextual方法被調用 追查調用鏈,還是在com.fasterxml.jackson.databind.deser.BeanDeserializerFactory#constructSettableProperty方法中被調用的,這和上一步創建HdxAesDataDeserializer對象是同一個方法,也就是中1標誌的位置,2處標誌的位置則是現在createContextual方法被調用的位置。 可以看到,在調用默認構造方法創建了HdxAesDataDeserializer對象之後,又調用了一次createContextual方法使用帶參數的構造方法創建了HdxAesDataDeserializer對象並替換了老的deser對象。 到這裏就明白了,原來createContextual方法返回新的JsonSerilizer對象是爲了替換掉老的對象。 3、deserialize方法最後被調用 這時候使用的deser對象已經是createContextual返回的對象了,就可以正常使用JavaType進行反序列化了。 四、總結 1、反序列化關鍵點 最重要的是反序列化工具要繼承 JsonDeserializer並且實現ContextualDeserializer接口,實現ContextualDeserializer接口實現的createContextual接口會創建新的 JsonDeserializer對象並且替換掉當前的this對象。 2、線程安全性問題 由於引入了額外的JavaType成員變量,可能會存在線程安全性問題,但是通過源碼可以得知,針對每個成員變量,如果默認的不支持,則會創建相應的單獨的序列化工具,也就不存在線程安全性問題了。
@Slf4j @AllArgsConstructor @NoArgsConstructor public class HdxAesDataDeserializer extends JsonDeserializer<Object> { @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { String valueAsString = p.getValueAsString(); String s = HdxAesUtil.decryptHex(valueAsString); return ObjectMapperFactory.getObjectMapper().readValue(s, ctxt.getContextualType()); } }
這個註解是加到字段上的,但是之前的一篇文章 spring mvc請求體偷樑換柱:HandlerMethodArgumentResolver 這個註解已經加到了請求參數上,所以再添加一個允許加註解到字段即可
這段代碼和原先是一樣的
/** * @author kdyzm * @date 2021/10/27 */ @Configuration public class JsonConfig { /** * @param builder * @return * @link {https://stackoverflow.com/questions/34965201/customize-jackson-objectmapper-to-read-custom-annotation-and-mask-fields-annotat} * @see JacksonAutoConfiguration.JacksonObjectMapperConfiguration#jacksonObjectMapper(Jackson2ObjectMapperBuilder) */ @Bean @Primary ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper mapper = builder.createXmlMapper(false).build(); AnnotationIntrospector sis = mapper.getSerializationConfig().getAnnotationIntrospector(); AnnotationIntrospector is1 = AnnotationIntrospectorPair.pair(sis, new HdxAesDataAnnotationIntrospector()); mapper.setAnnotationIntrospector(is1); return mapper; } }
上述步驟不多,但是似乎已經天衣無縫,信誓旦旦的來測試個
然後順利得到了一個空指針異常
最後debug得到的出問題的代碼在這裏,ctxt.getContextualType()獲取到的JavaType是空值。。
谷歌查了下,看到了有價值的github issue:Give Custom Deserializers access to the resolved target Class of the currently deserialized object
還有stackoverflow上的討論:How to create a general JsonDeserializer
這一切都指向了唯一一種解決方案:實現 ContextualDeserializer 接口,照葫蘆畫瓢,那就試試,改造後的代碼如下
ContextualDeserializer
/** * @author kdyzm * @date 2021/11/18 */ @Slf4j @AllArgsConstructor @NoArgsConstructor public class HdxAesDataDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer { private JavaType type; @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { String valueAsString = p.getValueAsString(); String s = HdxAesUtil.decryptHex(valueAsString); return ObjectMapperFactory.getObjectMapper().readValue(s, type); } @Override public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty beanProperty) throws JsonMappingException { //beanProperty is null when the type to deserialize is the top-level type or a generic type, not a type of a bean property JavaType type = deserializationContext.getContextualType() != null ? deserializationContext.getContextualType() : beanProperty.getMember().getType(); return new HdxAesDataDeserializer(type); } }
其實改完之後我是蒙圈的,我有幾點疑問
總之是騾子是馬,拉出來溜溜,這麼一改,果然就好用了,但是用起來不痛快,畢竟還存在着疑問呢,帶着疑惑,我進行了源碼追蹤。
在相關的代碼打上斷點
然後運行測試代碼
com.fasterxml.jackson.databind.util.ClassUtil#createInstance
這段代碼使用反射技術利用無參構造方法創建了HdxAesDataDeserializer對象。那麼調用時機如何呢,根據調用鏈繼續追蹤,可以看到調用點最終在這裏
這段代碼會單獨處理對象的每個成員變量的反序列化,然後每次都會在com.fasterxml.jackson.databind.deser.BeanDeserializerFactory#constructSettableProperty方法中尋找合適的反序列化工具
如果沒找到,則創建合適的反序列化工具
這說明了一個問題,每個成員變量在反序列化的時候如果是自定義的註解和反序列化類,每次都會新建反序列化類,也就不存在線程安全性問題了。
追查調用鏈,還是在com.fasterxml.jackson.databind.deser.BeanDeserializerFactory#constructSettableProperty方法中被調用的,這和上一步創建HdxAesDataDeserializer對象是同一個方法,也就是中1標誌的位置,2處標誌的位置則是現在createContextual方法被調用的位置。
可以看到,在調用默認構造方法創建了HdxAesDataDeserializer對象之後,又調用了一次createContextual方法使用帶參數的構造方法創建了HdxAesDataDeserializer對象並替換了老的deser對象。
到這裏就明白了,原來createContextual方法返回新的JsonSerilizer對象是爲了替換掉老的對象。
這時候使用的deser對象已經是createContextual返回的對象了,就可以正常使用JavaType進行反序列化了。
最重要的是反序列化工具要繼承 JsonDeserializer並且實現ContextualDeserializer接口,實現ContextualDeserializer接口實現的createContextual接口會創建新的 JsonDeserializer對象並且替換掉當前的this對象。 2、線程安全性問題 由於引入了額外的JavaType成員變量,可能會存在線程安全性問題,但是通過源碼可以得知,針對每個成員變量,如果默認的不支持,則會創建相應的單獨的序列化工具,也就不存在線程安全性問題了。
由於引入了額外的JavaType成員變量,可能會存在線程安全性問題,但是通過源碼可以得知,針對每個成員變量,如果默認的不支持,則會創建相應的單獨的序列化工具,也就不存在線程安全性問題了。
國內的情況就不用說了,基本上是很難找到可以用於研究的GIS數據資源的。要麼就是收費,免費的即使能找到,能否合法合規的進行使用也是一個問題。地理信息數據還是國外比較開放一些,相當多的政府組織或者公益機構對公衆開放了下載渠道,大家可以適度獲取並
引言 在當今快速發展的數字化時代,企業對業務應用的需求日益複雜且多元。低代碼開發平臺作爲一個創新的解決方案,以直觀易用的設計理念,打破了傳統的編程壁壘,讓非技術人員也能輕鬆構建功能完備的Web應用程序,無需深入編碼。這一特性極大地簡化了應用
可以通過計算每秒 window.requestAnimationFrame 的調用頻率來做爲 FPS 值。它接收一個回調函數,該回調函數會在瀏覽器下一次重繪之前執行。所以只要我們循環調用並記錄單位時間內的調用次數就能計算當前頁面的幀率了。
本文示例代碼已上傳至我的Github倉庫https://github.com/CNFeffery/dash-master 大家好我是費老師,不久前Dash發佈了其2.17.0版本,執行下面的命令進行最新版本Dash的安裝: pip
最近一直在用人大金倉做項目,相關的文檔相比其它流行的所謂“主流”數據庫來說還是少了點,記錄一些開發過程中遇到的問題。 數據庫的模式(database_mode)在實例創建後就確定好了的,不可更改。想要改變模式只能重新init一個實例。
1.簡介 分頁測試,這種一般都是公共的方法系統中都寫好了,這種一般出現是數據展示比較多的時候,會採取分頁的方法,而且比較固定,一般是沒有問題的,因此它非常適合自動化測試,但是如何使用playwright來進行分頁自動化測試了,宏哥今天就講解
# 問題:Ubuntu 18 使用自帶的共享桌面、VNC遠程桌面延遲、降低分辨率也無效。 # 方案:最後找到安裝 NoMachine的遠程桌面,解決遠程卡頓問題 根據自己操作系統 選擇NoMachine for Linux進行下載官網:ht
“malloc.c: No such file or directory.” 參考:https://www.cnblogs.com/gatsby123/p/11755320.html 安裝依賴 sudo apt-get install li
大數據面試SQL每日一題系列:最高峯同時在線主播人數。字節,快手等大廠高頻面試題 之後會不定期更新每日一題sql系列。 SQL面試題每日一題系列內容均來自於網絡以及實際使用情況收集,如有雷同,純屬巧合。 1.題目 問題1:如下爲某直播平臺各
事件背景 我以前只是在新聞看到過拖欠農民工工資這樣的事,但這次是發生在自己身上了! 今天晚上下班後,看見父母面露愁色,並認真的聽着父母的對話。 大概意思是就是爸爸跟着工程隊包天活已經完事有一段時間了,但是包天的工資一直不給,而且聽爸爸說那意
Canvas圖形編輯器-我的剪貼板裏究竟有什麼數據 在這裏我們先來聊聊我們究竟應該如何操作剪貼板,也就是我們在瀏覽器的複製粘貼事件,並且在此基礎上聊聊我們在Canvas圖形編輯器中應該如何控制焦點以及如何實現複製粘貼行爲。 在線編輯: h
組件介紹 PullToRefreshList允許用戶通過下拉動作來刷新列表內容,以及通過上拉動作來加載更多的數據。組件內部封裝了滾動監聽、狀態管理和動畫效果,使得開發者可以輕鬆集成到自己的項目中。 1. 實現思路 封裝成可複用的公共控件:
來自:百度 Docker 可以佈署在 Linux 系統上,也可以佈署在你自己的電腦上。 在 Linux 系統上佈署 Docker: 安裝 Docker: curl -fsSL https://get.docker.com -o get-d
DI依賴注入對我們後端程序員來說肯定是基礎中的基礎了,我們經常會使用下面的代碼注入相關的service services.AddScoped<Biwen.AutoClassGen.TestConsole.Services.TestServi
1.創建&刪除 MySQL可以通過CREATE、ALTER、DDL三種方式創建一個索引。 在MySQL中,使用CREATE INDEX語句可以創建索引。具體語法如下: CREATE INDEX indexName ON tableNam