原创

使用List.of()、Map.of()、Set.of() - Jackson无法反序列化Redis缓存(缓存有类型标识的时候)

温馨提示:
本文最后更新于 2020年03月18日,已超过 1,501 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

说明

List.of()Map.of()Set.of()
这三者都是从不可变集合的容器类ImmutableCollections衍生出来的。

如果Redis缓存序列化配置了携带类型一起存入Redis的话:

om.activateDefaultTyping(om.getPolymorphicTypeValidator(), DefaultTyping.NON_FINAL);

使用了List.of()Map.of()Set.of()ImmutableCollections衍生出来类将无法反序列化,Jackson会报错:

Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_ARRAY), expected VALUE_STRING: need JSON String that contains type id (for subtype of java.lang.Object)

举个例子

测试

比如,我有一个树节点实体类,并且我Redis缓存序列化和反序列化配置了om.activateDefaultTyping(om.getPolymorphicTypeValidator(), DefaultTyping.NON_FINAL);

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
public class SimpleTree {

  private Long id;
  private Long parentId;
  private String label;
  private List<SimpleTree> children;
}

假设有这么一些数据:

SimpleTree rootTreeDto = new SimpleTree();
rootTreeDto.setId(SysConst.ROOT_MENU_ID);
rootTreeDto.setLabel(SysConst.ROOT_MENU_NAME);
rootTreeDto.setChildren(treeDtoList);
List<SimpleTree> result = List.of(rootTreeDto);

// 测试放入缓存,放入缓存时没问题的
redisTemplate.opsForValue().set("simple_tree", result);
// 测试从缓存取出,报将会报错
Object simpleTreeObj = redisTemplate.opsForValue().get("simple_tree");

当从Redis中反序列化出数据的时候,会出现异常:

Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (START_ARRAY), expected VALUE_STRING: need JSON String that contains type id (for subtype of java.lang.Object)

ImmutableCollections相关源代码

那么就来看一下List.of(xxx)的源代码:

static <E> List<E> of(E e1) {
  return new ImmutableCollections.List12<>(e1);
}

ImmutableCollections源代码:

/**
 * Container class for immutable collections. Not part of the public API.
 * Mainly for namespace management and shared infrastructure.
 *
 * Serial warnings are suppressed throughout because all implementation
 * classes use a serial proxy and thus have no need to declare serialVersionUID.
 */
@SuppressWarnings("serial")
class ImmutableCollections {
    /**
     * A "salt" value used for randomizing iteration order. This is initialized once
     * and stays constant for the lifetime of the JVM. It need not be truly random, but
     * it needs to vary sufficiently from one run to the next so that iteration order
     * will vary between JVM runs.
     */
    static final int SALT;
    static {
        long nt = System.nanoTime();
        SALT = (int)((nt >>> 32) ^ nt);
    }

    /** No instances. */
    private ImmutableCollections() { }
    
    // 具体代码略...
}

ImmutableCollections.List12源代码:

static final class List12<E> extends AbstractImmutableList<E>
  implements Serializable {
  // 具体代码略...
}

ImmutableCollections解析

ImmutableCollections的无参构造方法是私有的,且final类型修饰的(说明它不能被继承),final修饰在这不是重点,重点是无参构造方法是私有的。
我们都知道Jackson反序列化需要一个无参构造方法,或者手动提供一个构造方法,然后使用注解@JsonCreator修饰,并且在构造方法的入参用@JsonProperty指明字段名称修饰。

简而言之,一句话:**ImmutableCollections是无法被反序列化的**!

这就是上面从Redis缓存中使用Jackson反序列化数据处理会报错的原因。

解决办法?

1.使用new ArrayList

new ArrayList(rootTreeDto)

2.使用guava提供的集合工具

Lists.newArrayList(rootTreeDto)

3.使用其它可被反序列化的集合、工具类或自定义反序列化

太多了办法了,略…

4.如果确实想反序列化这几个特殊类

可以参考:https://github.com/spring-projects/spring-security/tree/main/core/src/main/java/org/springframework/security/jackson2

自定义UnmodifiableListDeserializerUnmodifiableMapDeserializerUnmodifiableSetDeserializer实现JsonDeserializer类去达到特殊的结构去自定义反序列化。

也可以看看org.springframework.security.jackson2.CoreJackson2Module类。

参考文章:

本文目录