【发布时间】:2019-12-20 08:10:59
【问题描述】:
我正在编写一个应用程序,它将集成到一个 API,该 API 具有返回由 XSD 支持的 XML 的端点。我的应用程序最初必须针对这个的两个不同版本(可能在未来的版本中更多),并且这不必是动态的。当您启动应用程序时,用户必须告诉应用程序支持哪个 API 主要版本。 XSD 不在我的控制之下,我不想编辑它们。
XSD 生成同名的类,我已经遇到了问题。我无法将 XJC 生成的两个 ObjectFactory 类都加载到 JAXBContext 中。我的解决方案现在是一张 JAXBContexts 地图:
private static Map<Integer, Pair<Class<?>, JAXBContext>> contexts = new HashMap<>();
static {
try {
contexts.put(4, Pair.of(com.api.v4_2_0.ObjectFactory.class, JAXBContext.newInstance(com.api.v4_2_0.ObjectFactory.class)));
contexts.put(3, Pair.of(com.api.v3_9_4.ObjectFactory.class, JAXBContext.newInstance(com.api.v3_9_4.ObjectFactory.class)));
} catch (JAXBException e) {
LOGGER.error("Failed to initialize JAXBContext", e);
}
}
该对用于了解 JAXBContext 基于哪个类,因为我无法在运行时恢复它。然后,为了序列化一个对象,我使用了很多有效但感觉不对的魔法反射:
public static String objectToString(final Object object, final Integer majorVersion) {
try {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
getMarshallerForMajorVersion(majorVersion).marshal(createJaxbElementFromObject(object, getObjectFactoryForMajorVersion(majorVersion)), os);
return os.toString(UTF_8);
} catch (JAXBException e) {
throw SesameException.from("Failed to serialize object", e);
}
}
private static Marshaller getMarshallerForMajorVersion(final Integer majorVersion) throws JAXBException {
return getContextForMajorVersion(majorVersion).getRight().createMarshaller();
}
private static Class<?> getObjectFactoryForMajorVersion(final Integer majorVersion) {
return getContextForMajorVersion(majorVersion).getLeft();
}
private static Pair<Class<?>, JAXBContext> getContextForMajorVersion(final Integer majorVersion) {
if (contexts.containsKey(majorVersion)) {
return contexts.get(majorVersion);
}
throw illegalArgument("No JAXBContext for API with major version %d", majorVersion);
}
private static JAXBElement<?> createJaxbElementFromObject(final Object object, final Class<?> objectFactory) {
try {
LOGGER.info("Attempting to find a JAXBElement producer for class {}", object.getClass());
final Method method = findElementMethodInObjectFactory(object, objectFactory);
return (JAXBElement<?>) method.invoke(objectFactory.getConstructor().newInstance(), object);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException e) {
throw illegalArgument("Failed to construct JAXBElement for class %s", object.getClass().getName());
}
}
private static Method findElementMethodInObjectFactory(final Object object, final Class<?> left) {
return Arrays.stream(left.getMethods())
.filter(m -> m.getReturnType().equals(JAXBElement.class))
.filter(m -> m.getName().endsWith(object.getClass().getSimpleName()))
.findFirst()
.orElseThrow(() -> illegalArgument("Failed to find JAXBElement constructor for class %s", object.getClass().getName()));
}
这很好用,但感觉很脆弱。
问题
更糟糕的是必须使用泛型将 XML 反序列化为对象:
public static <T> T stringToObject(final String xml, final Class<T> toClass, final Integer majorVersion) {
try {
final Unmarshaller unmarshaller = getUnmarshallerForVersion(majorVersion);
final JAXBElement<T> unmarshalledElement = (JAXBElement<T>) unmarshaller.unmarshal(new StringReader(xml));
return toClass.cast(unmarshalledElement.getValue());
} catch (JAXBException e) {
throw SesameException.from(format("Failed to deserialize XML into %s", toClass.getCanonicalName()), e);
}
}
// And calling this from another class
private com.api.v4_2_0.SomeClass.class toSomeClass(final HttpResponse<String> response) {
return XmlUtil.stringToObject(response.body(), com.api.v4_2_0.SomeClass.class, apiMajorVersion); // <--- I can't beforehand use this package since major version might be 3.
}
现在(据我所知)我无法使用泛型并根据使用的 API 的主要版本将其映射到正确包中的正确类。
我也尝试过使用抽象基类并为每个版本只给它一个ObjectFactory,但这仍然给了我问题部分中描述的问题。我不知道如何返回该类的正确版本:
private com.api.v4_2_0.SomeClass.class toSomeClass(final HttpResponse<String> response) {
return version4XmlUtil.stringToObject(response.body(), com.api.v4_2_0.SomeClass.class); // <--- I can't beforehand use this package since major version might be 3.
}
如何构建我的代码来解决这个问题?什么模式有用?我是不是完全走错了路?
【问题讨论】: