根据上一篇文章在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成员变量,可能会存在线程安全性问题,但是通过源码可以得知,针对每个成员变量,如果默认的不支持,则会创建相应的单独的序列化工具,也就不存在线程安全性问题了。
Object.keys() 對象的遍歷 返回給定對象所有可枚舉屬性的數組;是屬性名組成的數組 let obj = { a: 1, b: 2, c: 3 }; Object.keys(obj).map((key) => {
有一個方格矩陣,矩陣邊界在無窮遠處。我們做如下假設: a、每走一步時,只能從當前方格移動一格,走到某個相鄰的方格上; b、走過的格子立即塌陷無法再走第二次; c、只能向北、東、西三個方向走; 請問:如果允許在方格矩陣上走n步,共有多少種不同
[Mellanox] 安裝MLNX_OFED 雖然已經安裝過很多遍了,但是這裏還是再次寫一遍安裝過程,方便以後查閱。 Mellanox的這堆東西其實每個安裝起來都不難,難點在於版本要匹配。所以最重要的是我們要知道1.我們需要哪個版本的驅動;
首先,大多數人的印象裏,hibernate作爲一個笨重學習成本高的近乎全自動的框架它的優點就是可以支持很多數據庫,但是最近研究發現,java中的boolean類型的字段,在mariadb/mysql 中爲bit 0/1,在sqlserver
1. 快捷方式需要正確的配置StartupWMClass屬性,那麼如何獲取這個屬性呢?參考如下命令 xprop | grep WM_CLASS 將終端程序小窗運行上述命令,鼠標點擊哪個應用窗體就會獲取哪個窗體的名稱,有可能會有多個,多個
筆.COOL,是一個最近在國內嶄露頭角的在線HTML/CSS/JS編輯器和作品分享平臺。 筆.COOL 提供了一個在線的 HTML、CSS 和 JavaScript 代碼編輯器。無需任何安裝,你只需打開網站,就可以開始編寫前端代碼。編輯
前言 今天大姚給大家分享四款Visual Studio中的代碼格式化工具、擴展插件。大家可以在Visual Studio中的管理擴展或者插件市場下載安裝。 代碼格式化工具的作用 自動調整代碼的佈局和風格,以確保代碼具有統一的格式,提高可讀性
大家好,我是R哥。 金三銀四結束了,上個月分享了一個 35K 入職的面試輔導案例: 35K*14 薪入職了,這公司只要不裁員,我能一直呆下去。。 今天再分享一個上個月讓人很有成就感的面試輔導 case: 外包、空窗四個月、薪資 10k、
https://blog.csdn.net/whatzhang007/article/details/110089447 總結就是一個字: 啓用json類的保存方式.改成logntext即可. 例如我的方穹項目的表設計: 不吐槽不行, 真
如果想做基於圖像cnn的深度強化學習,需要拿到gym的截圖,下面是兩種截圖方法。 1. 利用render結果生成圖像: import gym import warnings import os from PIL import Image
Windows 10 update history https://support.microsoft.com/en-gb/topic/windows-10-update-history-8127c2c6-6edf-4fdf-8b9f-0f
GitHub Copilot 是一個改變遊戲規則的人工智能助手,可以徹底改變您在 Visual Studio 中的編碼流程。在我們的視頻系列中,Bruno Capuano 探討了這個智能編碼夥伴如何幫助您更有效地編寫代碼,同時保持質量
最近看到一個比較有意思的 AI 項目,叫 AI 時間線,顧名思義,就是藉助 AI 來創建某個關鍵字的時間線。主頁界面很簡單,就是一個輸入框。 我在輸入辛亥革命後,就會生成下圖的時間線,將辛亥革命的各個關鍵點都列了出來。我看到這
從油猴腳本管理器的角度審視Chrome擴展 在之前一段時間,我需要藉助Chrome擴展來完成一個需求,當時還在使用油猴腳本與瀏覽器擴展之間調研了一波,而此時恰好我又有一些做的還可以的油猴腳本 TKScript (點個star吧 😁),相對會
收穫 瞭解 Pod 的狀態(Status) 瞭解 pod 階段(Phase) 瞭解 Pod conditions 瞭解容器狀態(Status) 保持容器健康 瞭解容器自動重啓 使用探活(liveness)探針(Probe)檢查容