一些主流的序列化框架的兼容
由于 Record 限制了序列化与反序列化的唯一方式,所以其实兼容起来很简单,比起 Java Class 改个结构,加个特性导致的序列化框架更改来说还要简单。
- Issue: Support for record types in JDK 14
- Pull Request: Support for record types in JDK 14
- 对应版本:jackson-databind-2.12.0
- Issue: Java 14 records : how to deal with them?
- Pull Request: Add support for Records in JDK 14
- 对应版本:kryo-5.1.0
- Issue: Support for record types in JDK 14
- Pull Request: Add support for Record types in JDK 14
- 对应版本:1.5.x,还未发布
这三个框架中实现对于 Record 的兼容思路都很类似,也比较简单,即:
- 实现一个针对 Record 的专用的 Serializer 以及Deserializer。
- 通过反射(Java Reflection)或者句柄(Java MethodHandle)验证当前版本的 Java 是否支持 Record,以及获取 Record 的规范构造函数(canonical constructor)以及各种 field 的 getter 进行反序列化和序列化。给大家两个工具类进行参考,分别是使用反射(Java Reflection)和句柄(Java MethodHandle)实现:
import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Comparator; import common.RecComponent; /** * Utility methods for record serialization, using Java Core Reflection. */ public class ReflectUtils { private static final Method IS_RECORD; private static final Method GET_RECORD_COMPONENTS; private static final Method GET_NAME; private static final Method GET_TYPE; static { Method isRecord; Method getRecordComponents; Method getName; Method getType; try { // reflective machinery required to access the record components // without a static dependency on Java SE 14 APIs Class<?> c = Class.forName("java.lang.reflect.RecordComponent"); isRecord = Class.class.getDeclaredMethod("isRecord"); getRecordComponents = Class.class.getMethod("getRecordComponents"); getName = c.getMethod("getName"); getType = c.getMethod("getType"); } catch (ClassNotFoundException | NoSuchMethodException e) { // pre-Java-14 isRecord = null; getRecordComponents = null; getName = null; getType = null; } IS_RECORD = isRecord; GET_RECORD_COMPONENTS = getRecordComponents; GET_NAME = getName; GET_TYPE = getType; } /** Returns true if, and only if, the given class is a record class. */ static boolean isRecord(Class<?> type) { try { return (boolean) IS_RECORD.invoke(type); } catch (Throwable t) { throw new RuntimeException("Could not determine type (" + type + ")"); } } /** * Returns an ordered array of the record components for the given record * class. The order is imposed by the given comparator. If the given * comparator is null, the order is that of the record components in the * record attribute of the class file. */ static <T> RecComponent[] recordComponents(Class<T> type, Comparator<RecComponent> comparator) { try { Object[] rawComponents = (Object[]) GET_RECORD_COMPONENTS.invoke(type); RecComponent[] recordComponents = new RecComponent[rawComponents.length]; for (int i = 0; i < rawComponents.length; i++) { final Object comp = rawComponents[i]; recordComponents[i] = new RecComponent( (String) GET_NAME.invoke(comp), (Class<?>) GET_TYPE.invoke(comp), i); } if (comparator != null) Arrays.sort(recordComponents, comparator); return recordComponents; } catch (Throwable t) { throw new RuntimeException("Could not retrieve record components (" + type.getName() + ")"); } } /** Retrieves the value of the record component for the given record object. */ static Object componentValue(Object recordObject, RecComponent recordComponent) { try { Method get = recordObject.getClass().getDeclaredMethod(recordComponent.name()); return get.invoke(recordObject); } catch (Throwable t) { throw new RuntimeException("Could not retrieve record components (" + recordObject.getClass().getName() + ")"); } } /** * Invokes the canonical constructor of a record class with the * given argument values. */ static <T> T invokeCanonicalConstructor(Class<T> recordType, RecComponent[] recordComponents, Object[] args) { try { Class<?>[] paramTypes = Arrays.stream(recordComponents) .map(RecComponent::type) .toArray(Class<?>[]::new); Constructor<T> canonicalConstructor = recordType.getConstructor(paramTypes); return canonicalConstructor.newInstance(args); } catch (Throwable t) { throw new RuntimeException("Could not construct type (" + recordType.getName() + ")"); } } } package invoke; import common.RecComponent; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Comparator; import static java.lang.invoke.MethodType.methodType; /** * Utility methods for record serialization, using MethodHandles. */ public class InvokeUtils { private static final MethodHandle MH_IS_RECORD; private static final MethodHandle MH_GET_RECORD_COMPONENTS; private static final MethodHandle MH_GET_NAME; private static final MethodHandle MH_GET_TYPE; private static final MethodHandles.Lookup LOOKUP; static { MethodHandle MH_isRecord; MethodHandle MH_getRecordComponents; MethodHandle MH_getName; MethodHandle MH_getType; LOOKUP = MethodHandles.lookup(); try { // reflective machinery required to access the record components // without a static dependency on Java SE 14 APIs Class<?> c = Class.forName("java.lang.reflect.RecordComponent"); MH_isRecord = LOOKUP.findVirtual(Class.class, "isRecord", methodType(boolean.class)); MH_getRecordComponents = LOOKUP.findVirtual(Class.class, "getRecordComponents", methodType(Array.newInstance(c, 0).getClass())) .asType(methodType(Object[].class, Class.class)); MH_getName = LOOKUP.findVirtual(c, "getName", methodType(String.class)) .asType(methodType(String.class, Object.class)); MH_getType = LOOKUP.findVirtual(c, "getType", methodType(Class.class)) .asType(methodType(Class.class, Object.class)); } catch (ClassNotFoundException | NoSuchMethodException e) { // pre-Java-14 MH_isRecord = null; MH_getRecordComponents = null; MH_getName = null; MH_getType = null; } catch (IllegalAccessException unexpected) { throw new AssertionError(unexpected); } MH_IS_RECORD = MH_isRecord; MH_GET_RECORD_COMPONENTS = MH_getRecordComponents; MH_GET_NAME = MH_getName; MH_GET_TYPE = MH_getType; } /** Returns true if, and only if, the given class is a record class. */ static boolean isRecord(Class<?> type) { try { return (boolean) MH_IS_RECORD.invokeExact(type); } catch (Throwable t) { throw new RuntimeException("Could not determine type (" + type + ")"); } } /** * Returns an ordered array of the record components for the given record * class. The order is imposed by the given comparator. If the given * comparator is null, the order is that of the record components in the * record attribute of the class file. */ static <T> RecComponent[] recordComponents(Class<T> type, Comparator<RecComponent> comparator) { try { Object[] rawComponents = (Object[]) MH_GET_RECORD_COMPONENTS.invokeExact(type); RecComponent[] recordComponents = new RecComponent[rawComponents.length]; for (int i = 0; i < rawComponents.length; i++) { final Object comp = rawComponents[i]; recordComponents[i] = new RecComponent( (String) MH_GET_NAME.invokeExact(comp), (Class<?>) MH_GET_TYPE.invokeExact(comp), i); } if (comparator != null) Arrays.sort(recordComponents, comparator); return recordComponents; } catch (Throwable t) { throw new RuntimeException("Could not retrieve record components (" + type.getName() + ")"); } } /** Retrieves the value of the record component for the given record object. */ static Object componentValue(Object recordObject, RecComponent recordComponent) { try { MethodHandle MH_get = LOOKUP.findVirtual(recordObject.getClass(), recordComponent.name(), methodType(recordComponent.type())); return (Object) MH_get.invoke(recordObject); } catch (Throwable t) { throw new RuntimeException("Could not retrieve record components (" + recordObject.getClass().getName() + ")"); } } /** * Invokes the canonical constructor of a record class with the * given argument values. */ static <T> T invokeCanonicalConstructor(Class<T> recordType, RecComponent[] recordComponents, Object[] args) { try { Class<?>[] paramTypes = Arrays.stream(recordComponents) .map(RecComponent::type) .toArray(Class<?>[]::new); MethodHandle MH_canonicalConstructor = LOOKUP.findConstructor(recordType, methodType(void.class, paramTypes)) .asType(methodType(Object.class, paramTypes)); return (T)MH_canonicalConstructor.invokeWithArguments(args); } catch (Throwable t) { throw new RuntimeException("Could not construct type (" + recordType.getName() + ")"); } } }