根據上一篇文章在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成員變量,可能會存在線程安全性問題,但是通過源碼可以得知,針對每個成員變量,如果默認的不支持,則會創建相應的單獨的序列化工具,也就不存在線程安全性問題了。
先把倉庫克隆到本地,我這邊還需要改cmake環境,在project上面加 set(CMAKE_CUDA_COMPILER /usr/local/cuda-11.8/bin/nvcc) 構建 mkdir build cmake -B bui
WPF編程-Prism 世有伯樂,然後有千里馬。千里馬常有,而伯樂不常有。 一、背景 Winform和WPF 1. WinForms和WPF 技術架構: WinForms是基於傳統的窗體和控件的技術,使用的是類
前言 做過.NET控制檯應用程序的同學應該都知道原生的.NET控制檯應用程序輸出的內容都比較的單調,假如要編寫漂亮且美觀的控制檯輸出內容或者樣式可能需要花費不少的時間去編寫代碼和調試。今天大姚給大家分享一個.NET開源且免費的類庫幫你輕鬆的
大家好,我是R哥。 說說最近的面試輔導,有個學員進了某個知名互聯網公司,拿到了 35K*14 薪的好成績,有不少粉絲留言問我,現在行情這麼差,他是怎麼做到的? 這篇拿他這個案例完整回顧一下吧,我管他叫小Y吧。 背景溝通 說下小Y的基本情況吧
選120hz的比60hz更護眼.
本週刊由 Python貓 出品,精心篩選國內外的 250+ 信息源,爲你挑選最值得分享的文章、教程、開源項目、軟件工具、播客和視頻、熱門話題等內容。願景:幫助所有讀者精進 Python 技術,並增長職業和副業的收入。 本期分享了 12 篇文
參考: https://blog.csdn.net/asdfaa/article/details/137884414 檢查系統是否支持 IPv6,查看被禁用了 在啓用 IPv6 之前,首先要確保您的系統支持 IPv6。要檢查內核
pl/0詞法分析器 下面是這個分析器的功能: 1、 待分析的簡單語言的詞法 (1) 關鍵字: begin if then while do end 所有關鍵字都是小寫。 (2) 運算符和界符: := + – * / < <= <> > >=
今天用VS2019編譯一個在VS2008下Coding的工程的時候,VS給出了一堆鏈接錯誤信息,如下圖所示的一些錯誤: Error 47 error LNK2019: unresolved external symbol "public
Windows平臺NASM彙編與C混合調用 tonyblackwhite 之前介紹了Windows平臺下,用微軟宏彙編MASM與C混合調用的方法。MASM是微軟獨有的,Linux沒法用,我喜歡學一個能夠應用於兩種平臺的,所以還是更鐘情於開源
在線客服系統我利用業餘時間斷斷續續做了好幾年,從一開始的追求完美,到後來的集中精力解決核心問題,從一開始的在每一個用戶身上投入大量時間,到後來學會分辨什麼是有價值客戶,學到很多,成長很多。 有許多工程技術上很好,很優秀的產品,甚至一定程度上
redis配置文件開啓鍵過期 # The "notify-keyspace-events" takes as argument a string that is composed # of zero or multiple charac
國內的IT企業逐漸的都有各種IM機器人,這些IM機器人會不斷的吐數據,但是這些吐數據最後都成了像垃圾消息或者周扒皮一樣的催命通知,完全沒有人性。我非常痛恨這種把IM裏不斷被催的方式,這種方式雖然能起作用,但是人在這種環境下工作真的就成了工具
來自:阿里的 通義靈碼 以下是幾種常見的複製數據類型到剪切板的方法: 複製文本到剪切板 using System.Windows.Forms; // 對於Windows Forms應用 // 或者 using System.Windows
@staticmethod def strSplit(textSource: str, patterns: str)->list: """ 分割字符串 :param