泛型类的 JAXB 序列化失败

Posted

技术标签:

【中文标题】泛型类的 JAXB 序列化失败【英文标题】:JAXB Serialization Failure with Generic Class 【发布时间】:2019-02-15 10:25:12 【问题描述】:

我正在尝试编写一个类,该类可以使用 Java 将设置序列化和反序列化为 XML。我已经用 C# 成功编写了这段代码,它非常有用,所以我想在我的 java 应用程序中使用类似的东西。

我有以下基类,我想序列化为 XML 的每个类都必须实现。

package serializers;

import java.lang.reflect.ParameterizedType;

abstract class XmlSerializableObject<T> 

    abstract T getDefault();

    abstract String getSerializedFilePath();

    String getGenericName() 
        return ((Class<T>) ((ParameterizedType) getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0]).getTypeName();
    

    ClassLoader getClassLoader() 
        return ((Class<T>) ((ParameterizedType) getClass()
            .getGenericSuperclass()).getActualTypeArguments()[0]).getClassLoader();
    

getGenericNamegetClassLoader 用于实例化JAXBContext。然后我有一个基本的实现作为设置提供者

public class SettingsProvider extends XmlSerializableObject<SettingsProvider> 

    private Settings settings;

    @Override
    public SettingsProvider getDefault() 
        return null;
    

    @Override
    public String getSerializedFilePath() 
        return "C:\\Data\\__tmp.settings";
    

    public Settings getSettings() 
        return settings;
    ;

    public void setSettings(Settings settings) 
        this.settings = settings;
    


class Settings 

    private String tmp;

    public String getTmp() 
        return tmp;
    

    public void setTmp(String tmp) 
        this.tmp = tmp;
    

现在我有以下序列化程序类

package serializers;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.File;

public class XmlSerializer 

    private static final Logger logger = LoggerFactory.getLogger(XmlSerializer.class);

    public static <T extends XmlSerializableObject> void Serialize(T o) 

        String filePath = o.getSerializedFilePath();
        File file = new File(filePath);

        try 
            String name = o.getGenericName();
            ClassLoader classLoader = o.getClassLoader();

            // THE FOLLOWING LINE throws.
            JAXBContext jaxbContext = JAXBContext.newInstance(name, classLoader); // also tried JAXBContext.newInstance(name);
            Marshaller jaxbMarshaller = jaxbContext.createMarshaller();

            jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            jaxbMarshaller.marshal(o, file);
         catch (JAXBException e) 
            logger.error("Serialization failed", e);
        
    

    // Deserialize below.

然后我有下面的测试来检查序列化的结果

package serializers;

import org.junit.Before;
import org.junit.Test;

public class XmlSerializerTest 

    private Settings settings = new Settings();
    private SettingsProvider provider;

    @Before
    public void setUp() throws Exception 
        settings.setTmp("testing");
        provider = new SettingsProvider();
        provider.setSettings(settings);
    

    @Test
    public void serialize() throws Exception 
        XmlSerializer.Serialize(provider);
    

问题是对JAXBContext jaxbContext = JAXBContext.newInstance(name, classLoader); 的调用引发了

javax.xml.bind.JAXBException:提供程序 com.sun.xml.internal.bind.v2.ContextFactory 无法实例化:javax.xml.bind.JAXBException:“serializers.SettingsProvider”不包含 ObjectFactory.class 或 jaxb 。指数 - 有关联的例外: [javax.xml.bind.JAXBException:“serializers.SettingsProvider”不包含 ObjectFactory.class 或 jaxb.in​​dex]

我尝试使用和不使用 ClassLoader 对象均无济于事。如何以这种方式序列化泛型类型?

感谢您的宝贵时间。

【问题讨论】:

【参考方案1】:

让我们看看抛出异常的那行代码:

JAXBContext jaxbContext = JAXBContext.newInstance(name);

在上面的代码行中,您传递的参数name 是要反序列化并在运行时确定的类的名称(即,给定示例中的serializers.SettingsProvider)。这可能不足以让 JAXB 确定构成 JAXB 上下文的所有类。因此,请尝试传递包含 JAXBContext 的此实例应反序列化的所有类的包的名称 -- 该包中的所有类都是您的 JAXB 上下文 .这是在编译时就知道的。因此,请尝试以下代码行:

JAXBContext jaxbContext = JAXBContext.newInstance("serializers");

这里,“serializers”是包含您要反序列化的所有类的包的名称,即给定示例的 JAXB 上下文

您可以参考Oracle JAXB tutorial 并注意以下代码行:

import primer.po.*;

...

JAXBContext jc = JAXBContext.newInstance( "primer.po" );

请参考Javadoc 并注意,如果要反序列化的类分布在多个包中,则应传递以冒号分隔的包名称列表,例如,--

JAXBContext.newInstance( "com.acme.foo:com.acme.bar" ) 

如果您必须传递类名而不是包名,请首先仔细阅读此Javadoc。请注意,JAXBContext 实例将仅使用作为参数传递的类以及可从这些类静态访问的类进行初始化。更喜欢以在编译时知道传递的类名的方式编写程序。

另外,注意 Java 中的泛型与 C# 中的泛型不同(尤其是 w.r.t 类型 erasure)可能对您有所帮助——请参阅 What is the concept of erasure in generics in Java?。 另外,给定类声明:

class XmlSerializableObject<T> 

其中声明类 XmlSerializableObject 处理类型 T,以下类声明:

class SettingsProvider extends XmlSerializableObject<SettingsProvider> 

其中声明 SettingsProvider 类处理自己的类型听起来很复杂。

或者你的意思是这样声明:

class SettingsProvider extends XmlSerializableObject<Settings> 

这表明类SettingsProvider 处理类型Settings

【讨论】:

感谢您的宝贵时间。你的回答很有帮助。【参考方案2】:

看起来应该是JAXBContext.newInstance(SettingsProvider.class)

该方法的JAXBContext.newInstance(String ...) 版本需要一个包名,正如错误消息所说,它应该包含一个ObjectFactory 类或jaxb.index 列表以将其引导到类。

【讨论】:

【参考方案3】:

你正在使用this newInstance method:

参数:

contextPath - java 包名称 列表,其中包含模式派生类和/或 java 到模式(JAXB 注释)映射类

classLoader - 这个类加载器将用于定位实现类。

所以 df778899 是对的,你不应该使用这个签名,因为 getGenericName 返回一个完全限定的类名而不是一个包。而且就算是包,你还是会错过ObjectFactory.classjaxb.index

但是JAXBContext.newInstance(SettingsProvider.class) 也不起作用。你会得到一个MarshalException 表示@XmlRootElement 丢失了

像这样注释SettingsProvider

@XmlRootElement(name = "root")
static class SettingsProvider extends XmlSerializableObject<SettingsProvider>


    private Settings settings;

    // [...]

最后你会得到:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
    <settings>
        <tmp>testing</tmp>
    </settings>
</root>

【讨论】:

【参考方案4】:

这是通过使用以下接口完成的

public interface IXmlSerializableObject 
    String getSerializedFilePath();

关键是

public interface IPersistanceProvider<T> extends IXmlSerializableObject 
    void save();
    void restoreDefaults();
    Class<T> getTypeParameterClass();

关键属性是Class&lt;T&gt; getTypeParameterClass()。然后在

中使用它
public static <T extends PersistanceProviderBase> void Serialize(T o) 
    String filePath = o.getSerializedFilePath();
    File file = new File(filePath);
    try 
        JAXBContext jaxbContext = JAXBContext.newInstance(o.getTypeParameterClass());
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();

        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        jaxbMarshaller.marshal(o, file);
     catch (JAXBException e) 
        logger.error("Serialization failed", e);
    

其中PersistanceProviderBase 实现了IPersistanceProvider 接口。

【讨论】:

以上是关于泛型类的 JAXB 序列化失败的主要内容,如果未能解决你的问题,请参考以下文章

gwt rpc 序列化泛型类

Jackson - 使用泛型类反序列化

fastjson反序列化多层嵌套泛型类与java中的Type类型

Java中让fastJson识别Colloction和Map中的泛型类

另一个泛型类的 Java 泛型类

泛型类的基本使用