java.lang.IllegalStateException: Failed to deserialize object type
这两天系统时不时的会报这个错误,具体的堆栈信息如下:
java.lang.IllegalStateException: Failed to deserialize object type
at org.springframework.util.SerializationUtils.deserialize(SerializationUtils.java:75)
at com.mljr.acs.msettle.core.utils.JedisUtil.get(JedisUtil.java:168)
at com.xxxx.xxxx.xxxx.web.action.FundTransferBillAction.getCacheAccountList(FundTransferBillAction.java:179)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
.........................
.........................
Caused by: java.lang.ClassNotFoundException: com.xxxx.xxxxx.base.model.customized.FundTransferModel
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1332)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1166)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:628)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1620)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at org.springframework.util.SerializationUtils.deserialize(SerializationUtils.java:69)
... 77 common frames omitted
看到最后的堆栈信息,可以知道根本原因在于没有找到FundTransferModel这个类,看了下报错的代码行,代码是这样的:
FundTransferModel transferBean = (FundTransferModel) jedisUtil.get(allocationUseId);
代码的目的是从redis缓存中获取对象,但是对象的路径并不是错误信息中那个找不到的路径:
//报错类路径
com.xxxx.xxxxx.base.model.customized.FundTransferModel
//实际路径
com.mljr.xxxx.xxxx.core.apps.base.model.customized.FundTransferModel
因为之前服务做过一次优化,重新调整了类路径,调整前的路径确实是报错的那个路径。
所以原因在于:
保存到redis中的对象是以报错类路径保存的,因为没有设置过超时时间,调整了类路径后,重新取出时转类型时就有问题了。后来把相关缓存内容清掉,重新写入了一次,之后就恢复正常了。
下面看下redis写入的实现:
private JedisPool pool ;
/**
*最大连接数
*/
public static final int CONFIG_MAX_ACTIVE = 500;
/**
* 最大空闲连接数,-1 表示无限制
*/
public static final int CONFIG_MAX_IDLE = -1;
/**
* 取一个连接的最长阻塞时间, milliseconds. 此处设置为10秒
*/
public static final int CONFIG_MAX_WAITE_MILLISECOND = 1000; // 10 seconds.
//redis 连接池相关参数配置
private Jedis getJedis() {
if (pool == null) {
config = new JedisPoolConfig();
config.setMaxTotal(CONFIG_MAX_ACTIVE);
config.setMaxIdle(CONFIG_MAX_IDLE);
config.setMaxWaitMillis(CONFIG_MAX_WAITE_MILLISECOND);
config.setBlockWhenExhausted(false);
JedisPool pool = new JedisPool(config, host, port, timeout, password);
}
jedis = pool.getResource();
return jedis;
}
//存储对象
public void setex(String key, Object object, int seconds) {
Jedis jedis = null;
try {
jedis = this.getJedis();
jedis.setex(key.getBytes(), seconds, SerializationUtils.serialize(object));
} catch (Exception e) {
logger.error("#设置缓存对象时发生错误", e);
} finally{
releaseJedis(jedis);
}
}
//获取缓存对象
public Object get(String key) {
Jedis jedis = null;
try {
jedis = this.getJedis();
return SerializationUtils.deserialize(jedis.get(key.getBytes()));
} catch (Exception e) {
logger.error("#获取缓存对象时发生错误", e);
return null;
} finally{
releaseJedis(jedis);
}
}
在存储前,用了序列化工具类对对象进行序列化然后存储,取出的时候再进行一次反序列化,序列化的工具类是Spring提供的工具类,实现如下所示:
SerializationUtils.java
public abstract class SerializationUtils {
/**
* Serialize the given object to a byte array.
* @param object the object to serialize
* @return an array of bytes representing the object in a portable fashion
*/
@Nullable
public static byte[] serialize(@Nullable Object object) {
if (object == null) {
return null;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.flush();
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to serialize object of type: " + object.getClass(), ex);
}
return baos.toByteArray();
}
/**
* Deserialize the byte array into an object.
* @param bytes a serialized object
* @return the result of deserializing the bytes
*/
@Nullable
public static Object deserialize(@Nullable byte[] bytes) {
if (bytes == null) {
return null;
}
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return ois.readObject();
}
catch (IOException ex) {
throw new IllegalArgumentException("Failed to deserialize object", ex);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Failed to deserialize object type", ex);
}
}
}
就是用 ByteArrayOutputStream 写做了序列化,用ByteArrayInputStream读做了反序列化,进入 ois.readObject() 查看读取对象的过程:
/**
* Read an object from the ObjectInputStream. The class of the object, the
* signature of the class, and the values of the non-transient and
* non-static fields of the class and all of its supertypes are read.
* Default deserializing for a class can be overridden using the writeObject
* and readObject methods. Objects referenced by this object are read
* transitively so that a complete equivalent graph of objects is
* reconstructed by readObject.
*
* 注释含义:
* 从ObjectInputStream读取一个对象。
* 对象所属类,类的签名,所有非transient修饰的变量,以及超类的相关信息都会被读取。
* 类的默认的反序列化方法可以使用writeObject 和 readObject 方法覆盖。
* 被该对象引用的对象能够等价的被readObject重构。
*
* <p>The root object is completely restored when all of its fields and the
* objects it references are completely restored. At this point the object
* validation callbacks are executed in order based on their registered
* priorities. The callbacks are registered by objects (in the readObject
* special methods) as they are individually restored.
*
* <p>Exceptions are thrown for problems with the InputStream and for
* classes that should not be deserialized. All exceptions are fatal to
* the InputStream and leave it in an indeterminate state; it is up to the
* caller to ignore or recover the stream state.
*
* @throws ClassNotFoundException Class of a serialized object cannot be
* found.
* @throws InvalidClassException Something is wrong with a class used by
* serialization.
* @throws StreamCorruptedException Control information in the
* stream is inconsistent.
* @throws OptionalDataException Primitive data was found in the
* stream instead of objects.
* @throws IOException Any of the usual Input/Output related exceptions.
*/
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
由第一段注释可知,在反序列化的时候,即 readObject 时,不只会读取对象的所有属性字段,对象的类信息和超类信息也会被读取。改造前后的对象(FundTransferModel)的属性值虽然相同,但是类信息是不同的,所以在转换类型的时候就会抛出 ClassNotFoundException 异常。
总结:
redis 存储信息时,如果只保留对象的部分信息,尽量不要保存对象本身。如需保存对象所有数据,可以保存对象的JSON串。上面的问题,如果是保存的JSON串,在读取的时候是不会关注对象的类信息的。
版权声明:本文为u010473656原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。