使用Kryo序列化iCal4j Calendar失败:缺少无参构造方法
ImmutableCalScale失败 项目中自定义了Calendar类,包含iCal4j的Calendar对象:
public class Calendar implements Serializable{ private static final long serialVersionUID = 1L; private net.fortuna.ical4j.model.Calendar calendar; private VTimeZone timezone; /* * constructor */ public Calendar() { super(); //Create a TimeZone TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry(); this.timezone = registry.getTimeZone("Europe/Berlin").getVTimeZone(); // Create a calendar this.calendar = createCalendar(); }
Kryo配置代码:
Kryo kryo = new Kryo(); kryo.setRegistrationRequired(false); kryo.setReferences(true); kryo.register(DateTime.class, new JodaDateTimeSerializer()); kryo.register(java.util.UUID.class, new UUIDSerializer()); kryo.register(net.fortuna.ical4j.model.component.VEvent.class, new Ical4jVEventSerializer()); kryo.register(net.fortuna.ical4j.model.property.CalScale.class, new JavaSerializer()); EstateManager earth = (EstateManager) o; Output output = new Output(new FileOutputStream(filekyro)); kryo.writeClassAndObject(output, earth.getApplicants().get(0)); output.close();
执行序列化时抛出异常:
com.esotericsoftware.kryo.KryoException: Class cannot be created (missing no-arg constructor): net.fortuna.ical4j.model.property.CalScale$ImmutableCalScale Serialization trace: properties (net.fortuna.ical4j.model.Calendar) calendar (core.Calendar) calendar (core.EstateManager) earth (core.Country) country (core.City) city (core.Applicant) at com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy.newInstantiatorOf(DefaultInstantiatorStrategy.java:111) at com.esotericsoftware.kryo.Kryo.newInstantiator(Kryo.java:1190) at com.esotericsoftware.kryo.Kryo.newInstance(Kryo.java:1199) at com.esotericsoftware.kryo.serializers.FieldSerializer.create(FieldSerializer.java:163) at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:122)
尝试注册ImmutableCalScale但提示无法解析类型,使用JavaSerializer也无效,求可行解决方案。
1. 替换实例化策略绕过无参构造器要求
Kryo默认的DefaultInstantiatorStrategy要求类必须有无参构造器,而ImmutableCalScale是私有内部类且没有无参构造。可以替换为Objenesis的实例化策略,它能在不调用构造器的情况下创建对象:
// 使用StdInstantiatorStrategy(Kryo内置,无需额外依赖) kryo.setInstantiatorStrategy(new StdInstantiatorStrategy()); // 或者如果需要更强的兼容性,引入Objenesis库后使用: // kryo.setInstantiatorStrategy(new ObjenesisInstantiatorStrategy());
若使用ObjenesisInstantiatorStrategy,需添加Maven依赖:
<dependency> <groupId>org.objenesis</groupId> <artifactId>objenesis</artifactId> <version>3.3</version> </dependency>
2. 为CalScale编写自定义Kryo序列化器
CalScale本质是枚举风格的类,ImmutableCalScale是其常量实例。可以自定义序列化器,直接序列化实例的标识字符串,反序列化时通过iCal4j提供的工厂方法获取实例:
public class CalScaleSerializer extends Serializer<CalScale> { @Override public void write(Kryo kryo, Output output, CalScale calScale) { // 序列化CalScale的标识值,如"GREGORIAN" output.writeString(calScale.getValue()); } @Override public CalScale read(Kryo kryo, Input input, Class<? extends CalScale> type) { // 通过标识值获取对应的CalScale单例实例 return CalScale.getInstance(input.readString()); } }
注册该序列化器:
kryo.register(net.fortuna.ical4j.model.property.CalScale.class, new CalScaleSerializer());
这种方式无需关心内部类ImmutableCalScale,完全基于iCal4j的API实现,兼容性强。
3. 通过反射注册ImmutableCalScale类
若要直接处理该内部类,可通过反射获取其Class对象并注册序列化器:
try { Class<?> immutableCalScaleClass = Class.forName("net.fortuna.ical4j.model.property.CalScale$ImmutableCalScale"); kryo.register(immutableCalScaleClass, new JavaSerializer()); } catch (ClassNotFoundException e) { e.printStackTrace(); }
注意:该方法依赖类的全限定名,若iCal4j后续版本修改内部类结构会失效,不如方案2可靠。
4. 借助iCal4j自身序列化机制
既然ical4j-serializer不兼容Kryo,可以先将iCal4j的Calendar对象序列化为字符串/字节数组,再用Kryo序列化该结果。为自定义Calendar类编写序列化器:
public class CustomCalendarSerializer extends Serializer<Calendar> { @Override public void write(Kryo kryo, Output output, Calendar customCal) { // 用iCal4j自带工具将Calendar序列化为字符串 StringWriter writer = new StringWriter(); try { new CalendarOutput(writer).write(customCal.getCalendar()); } catch (IOException e) { throw new RuntimeException(e); } output.writeString(writer.toString()); // 序列化timezone字段 kryo.writeObject(output, customCal.getTimezone()); } @Override public Calendar read(Kryo kryo, Input input, Class<? extends Calendar> type) { Calendar customCal = new Calendar(); // 反序列化iCal4j的Calendar对象 String icalStr = input.readString(); CalendarBuilder builder = new CalendarBuilder(); try { customCal.setCalendar(builder.build(new StringReader(icalStr))); } catch (IOException | ParserException e) { throw new RuntimeException(e); } // 反序列化timezone字段 customCal.setTimezone(kryo.readObject(input, VTimeZone.class)); return customCal; } }
注册该序列化器:
kryo.register(core.Calendar.class, new CustomCalendarSerializer());
这种方式完全利用iCal4j自身的序列化逻辑,避免处理内部类问题,兼容性最好,但序列化后的体积会比直接用Kryo略大。
内容的提问来源于stack exchange,提问作者user3740082

