Closed fallsea closed 3 years ago
@fallsea: Can you reproduce this issue in a testcase?
@theigl
The following code is the test case,Sets.newConcurrentHashSet()
Is the implementation of safe collection of guava
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.Set;
import org.objenesis.strategy.StdInstantiatorStrategy;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.google.common.collect.Sets;
public class Test {
public static void main(String[] args) throws IOException {
Kryo kryo = new Kryo();
kryo.setReferences(false);
kryo.setRegistrationRequired(false);
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
Demo demo = new Demo();
Set<String> set = Sets.newConcurrentHashSet();//guava tools
set.add("demo");
demo.setSet(set);
//serialize
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Output output = new Output(byteArrayOutputStream);
kryo.writeObject(output, demo);
output.close();
byte[] data = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
//deserialized
Input input = new Input(data);
Demo demo2= kryo.readObject(input, Demo.class);
System.err.println(demo2.getSet());
}
}
class Demo implements Serializable{
private static final long serialVersionUID = 4196944830626095055L;
private Set<String> set;
public Set<String> getSet() {
return set;
}
public void setSet(Set<String> set) {
this.set = set;
}
}
@fallsea: Thanks for the testcase!
The problem is that Kryo doesn't have a built-in serializer for Collections.newSetFromMap()
. Implementing such a serializer is not trivial because the map underlying the set cannot be accessed without reflection and the concrete implementation may be JDK dependent.
See #221 and #646.
As a workaround you can fall back to the JavaSerializer
for this class:
kryo.register(Collections.newSetFromMap(new HashMap<>()).getClass(), new JavaSerializer());
Alternatively, you can register a custom serializer for the class. Something like this should do the job, but it might not work on all JDKs:
public static class SetFromMapSerializer extends Serializer<Set> {
private static final Class SOURCE_CLASS;
private static final Field SOURCE_MAP_FIELD;
private static final Field SOURCE_SET_FIELD;
static {
try {
SOURCE_CLASS = Class.forName("java.util.Collections$SetFromMap");
SOURCE_MAP_FIELD = SOURCE_CLASS.getDeclaredField("m");
SOURCE_MAP_FIELD.setAccessible(true);
SOURCE_SET_FIELD = SOURCE_CLASS.getDeclaredField("s");
SOURCE_SET_FIELD.setAccessible(true);
} catch (ClassNotFoundException | IllegalAccessError | NoSuchFieldException e) {
throw new KryoException("Could not access java.util.Collections$SetFromMap.", e);
}
}
public void write (Kryo kryo, Output output, Set object) {
try {
kryo.writeClassAndObject(output, SOURCE_MAP_FIELD.get(object));
} catch (IllegalAccessException e) {
throw new KryoException("Could not access source" +
" map field in java.util.Collections$SetFromMap.", e);
}
}
public Set read (Kryo kryo, Input input, Class<? extends Set> type) {
final Map map = (Map)kryo.readClassAndObject(input);
final Set o = (Set)kryo.newInstance(SOURCE_CLASS);
try {
SOURCE_MAP_FIELD.set(o, map);
SOURCE_SET_FIELD.set(o, map.keySet());
} catch (IllegalAccessException e) {
throw new KryoException("Could not access source" +
" fields in java.util.Collections$SetFromMap.", e);
}
return o;
}
}
Set<String> set = Sets.newConcurrentHashSet();
After creating a collection in this way, the following exception occurs in deserialization. How can I solve it?