Jackson反序列化问题

本文探讨了Java内部类的不同类型及其特点,特别是非静态内部类为何不适合用于序列化和反序列化,以及Jackson如何处理这些类。

本文翻译于一片国外文章,原文链接如下:

JackSon和内部类,是的,你可以使用,但他们必须是静态的内部类。


这是我直到今天没有意识到的东西:这显然是正确使用内部类在Java开发者社区的巨大的困惑。事实上有一些网页显示

JackSon不能使用内部类值。

这实际上是真实的和虚假的,原因就在于Java的内部类的特点。早在Java 1.1的时候就引入了内部类(为了使AWT以及后

来的Swing更容易使用),但不是每个人都懂得如何适当地使用内部类。

1.长话短说

你可以这样使用静态内部类:
public class Outer {
    static class Inner {
        public int getX() { return 5;
    }
}    
值没有任何问题。只要确保“静”在那里。

如果你的内部类不是静态的,那么所得到的类通常是JackSon以及任何其他数据绑定框架无用(Hibernate,JAXB)不可以

使用的;通常可以被序列化,但不可以反序列化。要理解为什么,需要我们回到过去,90年代末…第一批语法糖加入Java



2.匿名的,静态的,非静态内部类

一共有三种内部类形式: 匿名内部类是在一条语句中使用的 (如事件处理函数); 静态内部类是用static修饰符修饰的类

; 普通内部类类似静态内部类,区别在于没有static修饰.

这里最重要的区别在于有么有static修饰. 修饰符选择不是很直观的, 它真正的意思是:非静态内部类(包括匿名内部类

)被编译器通过隐藏的构造器传递了一组隐藏变量.直接结果就是, 无默认构造函数 -- 即使代码里面确实有一个无参的

构造函数可以使用。

所以,如果你的代码是这样写的:

  public class Outer {
   class Inner { // non-static
     public int getX() { return 3; }
   }
  }

实际上编译器会这样生成:

public class Outer { ... }

class Outer$Inner {
  private final Outer parent;

  Outer$Inner(Outer p) {
    parent = p;
  }
  public int getX() { return 3; }
}

(同样的,内部类可以访问外部类的所有代码)

为什么会这样? 因为这样内部类内部就可以访问外部类的所有代码,包括私有代码;在匿名的情况下(内置)内部类,

甚至看似范围内的变量(这只是雾里看花 - 最后变量作为传递只是更隐蔽构造函数的参数)。

静态内部类只是普通的类,无任何隐藏内容。实际上它跟二级类(含有公开类的文件中声明的非公开类,使用封闭的命名

空间)除名字以外并无本质区别。

3。所以关于JackSon,内部类…?

JackSon拒绝尝试使用非静态内部类的基本原因(序列化是可以正常工作的)是因为这样的类没有实例化的一般方法,没

有零参数的构造函数,也没有@jsoncreator注释其他构造函数或工厂方法(或单个字符串参数的构造函数),所以

JackSon不能实例化它们。

在理论上可以如果得知所包围的父类实例是什么,那么就可以处理这种情况。但实际上这是复杂的和不必要的——典型的

static缺失是意外或者疏忽,通过增加给内部类增加static就可以让代码很好的工作。

<think>嗯,用户现在想了解如何在Jackson反序列化时使用前置处理器。根据之前的对话,我之前已经介绍了如何通过自定义CharacterEscapes和序列化器来处理反斜杠转义的问题,以及用户提供的引用内容。用户提到的引用里有一个例子,通过扩展Jackson2JsonMessageConverter,在fromMessage方法中添加处理逻辑,比如设置用户上下文。这可能就是用户所说的前置处理器的例子。 首先,我需要确认Jackson本身是否有官方的“前置处理器”概念。通常Jackson反序列化过程可以通过自定义反序列化器或者使用模块来注入自定义逻辑。用户可能希望在进行反序列化之前执行一些操作,例如修改输入数据、验证数据、设置线程上下文等。 在用户提供的引用[1]中,CustomMessageConverter覆盖了fromMessage方法,在调用父类反序列化之前,先处理了headers中的用户信息。类似地,在Jackson中,可能需要自定义JsonDeserializer,在deserialize方法中执行前置逻辑,比如修改JsonParser的内容,或者执行某些验证。 接下来,我需要考虑如何将这种逻辑应用到Jackson反序列化过程中。可能的方案包括: 1. 自定义反序列化器:继承JsonDeserializer,在deserialize方法开始时执行前置处理,然后调用默认的反序列化逻辑。 2. 使用BeanDeserializerModifier:通过模块系统,在创建Bean反序列化器时添加前置处理逻辑。 3. 使用@JsonDeserialize注解指定自定义的反序列化器。 同时,用户提供的引用[2]和[3]涉及到替换或扩展Spring MVC的消息转换器,可能用户的应用场景是在Spring环境中,需要将自定义的反序列化逻辑集成到HttpMessageConverter中,比如MappingJackson2HttpMessageConverter。 需要结合用户的实际需求,比如他们是否在Spring框架下使用,或者只是纯Jackson库的应用。根据用户之前的问题,他们可能是在Spring中使用Jackson进行JSON处理,因此可能需要通过配置Spring的MessageConverter来应用自定义的反序列化逻辑。 另外,用户提到“前置处理器”,可能希望在反序列化对象之前执行某些操作,如数据清洗、安全检查(如XSS过滤)、设置上下文信息等。例如,在引用[2]中提到的XSS过滤,可能在反序列化过程中对字符串进行清理。 可能的步骤包括: - 创建自定义的JsonDeserializer,在deserialize方法中添加前置处理。 - 将该自定义反序列化器注册到ObjectMapper中,可以通过模块的方式。 - 在Spring环境中,可能需要自定义HttpMessageConverter并替换默认的Jackson转换器,以确保前置逻辑被应用。 需要注意的是,Jackson反序列化流程默认不支持类似拦截器的前置处理器,但可以通过组合自定义反序列化器和模块系统来实现类似功能。例如,通过BeanDeserializerModifier来修改反序列化器的行为,添加前置处理逻辑。 此外,用户可能混淆了反序列化过程中的不同阶段。例如,在Spring的MessageConverter中,可以在转换消息之前执行逻辑,如引用[1]中的例子,而在纯Jackson中,则需要在反序列化器内部处理。 总结,可能的解决方案包括: 1. 自定义JsonDeserializer,在反序列化方法中执行前置处理。 2. 使用Jackson模块注册自定义的反序列化器。 3. 在Spring中配置自定义的HttpMessageConverter,集成前置处理逻辑。 需要提供具体的代码示例,比如如何编写自定义反序列化器,如何注册模块,以及如何在Spring中配置。同时,要指出不同方法的适用场景,比如全局处理还是特定字段处理,以及性能影响等。 另外,用户可能的需求是在反序列化整个对象之前执行某些操作,比如根据JSON内容动态调整反序列化的行为,或者记录日志,验证数据等。因此,示例应涵盖这些场景。 最后,需要验证这些方法是否有效,比如在自定义反序列化器中添加前置逻辑后,确保其被正确调用,并且不影响原有的反序列化流程。同时,要注意Jackson版本兼容性,以及与其他模块或配置的兼容性。</think>### Jackson 反序列化前置处理器的实现方法 在 Jackson 反序列化过程中实现前置处理逻辑,可通过以下三种核心方案实现,覆盖全局预处理、字段级预处理和 Spring 集成场景: --- #### 方案一:自定义反序列化器(字段级预处理) 通过继承 `JsonDeserializer`,在反序列化方法中插入前置逻辑。适用于**特定字段**的预处理。 **实现步骤:** 1. **定义前置处理逻辑** 在自定义反序列化器中添加预处理逻辑(例如:数据清洗、日志记录): ```java import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import java.io.IOException; public class CustomStringDeserializer extends JsonDeserializer<String> { @Override public String deserialize(JsonParser p, DeserializationContext ctx) throws IOException { // 前置处理:例如过滤特殊字符 String rawValue = p.getValueAsString(); String cleanedValue = rawValue.replace("\\", "-"); // 将反斜杠替换为横线 // 返回处理后的值(或调用默认反序列化逻辑) return cleanedValue; } } ``` 2. **通过注解绑定到字段** 在需要预处理的字段上添加 `@JsonDeserialize` 注解: ```java public class MyData { @JsonDeserialize(using = CustomStringDeserializer.class) private String content; // getter/setter 省略 } ``` **适用场景** 需对特定字段进行 XSS 过滤、字符替换等操作时[^2]。 --- #### 方案二:全局反序列化拦截器(Bean 预处理) 通过 `BeanDeserializerModifier` 实现全局预处理逻辑,适用于**所有对象反序列化前**的统一处理。 **实现步骤:** 1. **定义反序列化拦截器** 重写 `BeanDeserializerModifier`,在反序列化前注入逻辑: ```java import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.DeserializationConfig; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; public class PreProcessDeserializerModifier extends BeanDeserializerModifier { @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) { return new StdDeserializer<Object>(beanDesc.getBeanClass()) { @Override public Object deserialize(JsonParser p, DeserializationContext ctx) throws IOException { // 全局前置处理:例如记录日志 System.out.println("反序列化对象类型: " + beanDesc.getBeanClass().getName()); // 调用原始反序列化逻辑 return deserializer.deserialize(p, ctx); } }; } } ``` 2. **注册模块到 ObjectMapper** 将拦截器封装为 Jackson 模块: ```java SimpleModule module = new SimpleModule(); module.setDeserializerModifier(new PreProcessDeserializerModifier()); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(module); // 测试反序列化 MyData data = objectMapper.readValue(jsonString, MyData.class); ``` **适用场景** 需统一记录反序列化日志、添加审计信息或权限校验时[^1]。 --- #### 方案三:Spring MVC 消息转换器集成(HTTP 请求预处理) 通过替换 Spring 的 `HttpMessageConverter`,在 JSON 反序列化前插入业务逻辑,适用于**HTTP 请求参数预处理**。 **实现步骤:** 1. **自定义消息转换器** 继承 `MappingJackson2HttpMessageConverter`,重写 `readInternal` 方法: ```java import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.util.StreamUtils; import java.io.IOException; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; public class CustomMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter { @Override public Object readInternal(Class<?> clazz, Type type, JsonInputMessage inputMessage) throws IOException { // 前置处理:例如提取请求头信息 String rawJson = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8); String modifiedJson = rawJson.replace("\\", "-"); // 替换反斜杠 // 调用父类反序列化逻辑 return super.readInternal(clazz, type, new ModifiedJsonInputMessage(modifiedJson)); } // 自定义输入消息包装类(需实现 HttpInputMessage 接口) static class ModifiedJsonInputMessage implements HttpInputMessage { private final String content; public ModifiedJsonInputMessage(String content) { this.content = content; } // 实现接口方法... } } ``` 2. **替换默认转换器** 在 Spring 配置中移除默认转换器,添加自定义实现: ```java @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // 移除默认的 Jackson 转换器 converters.removeIf(c -> c instanceof MappingJackson2HttpMessageConverter); // 添加自定义转换器 converters.add(new CustomMappingJackson2HttpMessageConverter()); } } ``` **适用场景** 处理 HTTP 请求时需统一过滤 XSS、修改原始 JSON 数据等操作[^2]。 --- ### 方案对比与选型建议 | 方案 | 作用范围 | 侵入性 | 适用场景 | |--------------------|------------|--------|------------------------------| | 自定义反序列化器 | 字段级 | 低 | 特定字段的清洗、转换 | | Bean 反序列化拦截器| 全局 | 中 | 统一日志、权限校验 | | Spring 消息转换器 | HTTP 请求 | 高 | 请求级数据过滤或修改 | --- ### 注意事项 1. **性能影响** 全局拦截器和 Spring 消息转换器的预处理逻辑需避免复杂计算,防止影响反序列化性能。 2. **数据完整性** 修改原始 JSON 数据时需确保不破坏数据结构(如替换 `\` 可能影响文件路径等场景)[^3]。 3. **与序列化逻辑配合** 若需保证序列化与反序列化的对称性,需同步实现对应的后置处理器。 --- ### 相关问题 1. 如何通过 Jackson 实现动态字段过滤(如根据权限隐藏敏感字段)? 2. Jackson 中 `@JsonView` 注解如何实现多场景数据序列化控制? 3. 如何在 Spring Boot 中全局处理 JSON 反序列化异常?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值