- redisTemplate封装指令和redis connect原生指令混用导致json解析失败
- 存Long取出时是Integer及类似问题
redis connect原生指令在存储值时要注意转json格式,单行字符串不转json要记得两边加双引号,否则redisTemplate取数据json转对象时会解析失败。
存Long取出时是Integer及类似问题项目中redis序列化配置组件统一使用
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =newJackson2JsonRedisSerializer(Object.class);
MapobjectMaps = redisTemplate. opsForHash().entries("hashkey"); spaceId = objectMaps.get(key)
会出BUG
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
一步一步看源码(DEBUG过程中不涉及到的代码会删除)
首先将redis中数据转成转成Map
class DefaultHashOperationsextends AbstractOperations implements HashOperations { DefaultHashOperations(RedisTemplate template) { super((RedisTemplate ) template); } AbstractOperations(RedisTemplate template) { this.template = template; } public Map entries(K key) { byte[] rawKey = rawKey(key); Map entries = execute(connection -> connection.hGetAll(rawKey)); return entries != null ? deserializeHashMap(entries) : Collections.emptyMap(); } }
反序列化代码
调用反序列代码时没有指定
问题出在下面的代码。
举个例子
下面一段代码(称为代码one)会绕过类型检查,不会抛出异常和警告,其中类A是上面class DefaultHashOperations的简写,类B是下面反序列化代码的简写,代码one这种编写方式使得A类调用B类时不会发出异常和警告,只会在Map拿出来数据时出现类型转换错误。
class A {
B=new B();
@Test
public void aa() {
Map map=i.returnMap();
}
}
class B{
public Map returnMap(){
return new HashMap<>();
}
}
下面一段代码只会给出警告,在取值时会抛出异常。
但为什么在使用redis api时没有抛出警告?
首先,类似于代码one的编写方式不会有任何异常和警告,完全越过了类型检查。
其次,反序列化中很多方法上使用了@SuppressWarnings("unchecked")注解,这个注解会抑制警告,不是很理解为什么要抑制警告,很差劲的编写习惯。
HashMap hashMap=new HashMap<>(); hashMap.put(1521,"afdsdfa"); MapobjectMaps = hashMap;
abstract class AbstractOperations{ @SuppressWarnings("unchecked") Map deserializeHashMap(@Nullable Map entries) { // connection in pipeline/multi mode if (entries == null) { return null; } Map map = new LinkedHashMap<>(entries.size()); for (Map.Entry entry : entries.entrySet()) { map.put((HK) deserializeHashKey(entry.getKey()), (HV) deserializeHashValue(entry.getValue())); } return map; } @SuppressWarnings("unchecked") HK deserializeHashKey(byte[] value) { return (HK) hashKeySerializer().deserialize(value); } @SuppressWarnings("unchecked") HV deserializeHashValue(byte[] value) { return (HV) hashValueSerializer().deserialize(value); } RedisSerializer hashKeySerializer() { return template.getHashKeySerializer(); } RedisSerializer hashValueSerializer() { return template.getHashValueSerializer(); } }
Jackson2JsonRedisSerializer中序列化类型在上文项目配置构造方法中指定为 Object.class ,除此之外提供没有别的途径指定反序列化后得到的类型。
public class Jackson2JsonRedisSerializerimplements RedisSerializer { private final JavaType javaType; public Jackson2JsonRedisSerializer(Class type) { this.javaType = getJavaType(type); } @SuppressWarnings("unchecked") public T deserialize(@Nullable byte[] bytes) throws SerializationException { return (T) this.objectMapper.readValue(bytes, 0, bytes.length, javaType); } }
public class ObjectMapper extends ObjectCodec implements Versioned,java.io.Serializable
{
public T readValue(byte[] src, int offset, int len, JavaType valueType)
throws IOException, StreamReadException, DatabindException
{
return (T) _readMapAndClose(_jsonFactory.createParser(src, offset, len), valueType);
}
protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
throws IOException
{
try (JsonParser p = p0) {
final Object result;
final DeserializationConfig cfg = getDeserializationConfig();
final DefaultDeserializationContext ctxt = createDeserializationContext(p, cfg);
JsonToken t = _initForReading(p, valueType);
if (t == JsonToken.VALUE_NULL) {
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
} else {
result = ctxt.readRootValue(p, valueType,_findRootDeserializer(ctxt, valueType), null);
}
return result;
}
}
public abstract class DefaultDeserializationContext extends DeserializationContext implements java.io.Serializable{
public Object readRootValue(JsonParser p, JavaType valueType,
JsonDeserializer
到这一步,涉及变量类型的只有最初传进来的Obejct.class和下面构造方法定义的四种基本类型,再往下的代码看不懂,但基本可以断定如果没有指定特定类型就会转化为Boolean,Integer,Double,String。
public class AbstractDeserializer extends JsonDeserializer
这是最底层,但我现在看不懂…
public abstract class DeserializationContext
extends DatabindContext
implements java.io.Serializable
{
public Object handleMissingInstantiator(Class> instClass, ValueInstantiator valueInst,
JsonParser p, String msg, Object... msgArgs)
throws IOException
{
if (p == null) {
p = getParser();
}
msg = _format(msg, msgArgs);
LinkedNode h = _config.getProblemHandlers();
while (h != null) {
// Can bail out if it's handled
Object instance = h.value().handleMissingInstantiator(this,
instClass, valueInst, p, msg);
if (instance != DeserializationProblemHandler.NOT_HANDLED) {
// Sanity check for broken handlers, otherwise nasty to debug:
if (_isCompatible(instClass, instance)) {
return instance;
}
reportBadDefinition(constructType(instClass), String.format(
"DeserializationProblemHandler.handleMissingInstantiator() for type %s returned value of type %s",
ClassUtil.getClassDescription(instClass),
ClassUtil.getClassDescription((instance)
)));
}
h = h.next();
}
// 16-Oct-2016, tatu: This is either a definition problem (if no applicable creator
// exists), or input mismatch problem (otherwise) since none of existing creators
// match with token.
// 24-Oct-2019, tatu: Further, as per [databind#2522], passing `null` ValueInstantiator
// should simply trigger definition problem
if (valueInst == null ) {
msg = String.format("Cannot construct instance of %s: %s",
ClassUtil.nameOf(instClass), msg);
return reportBadDefinition(instClass, msg);
}
if (!valueInst.canInstantiate()) {
msg = String.format("Cannot construct instance of %s (no Creators, like default constructor, exist): %s",
ClassUtil.nameOf(instClass), msg);
return reportBadDefinition(instClass, msg);
}
msg = String.format("Cannot construct instance of %s (although at least one Creator exists): %s",
ClassUtil.nameOf(instClass), msg);
return reportInputMismatch(instClass, msg);
}



