JAXB序列化泛型类出错

4

我正在尝试编写一个类,使用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();
    }
}

在使用JAXBContext实例化时,getGenericNamegetClassLoader可用。我已经编写了一个基本的实现作为设置提供程序。

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.index - 带有关联异常: [javax.xml.bind.JAXBException:"serializers.SettingsProvider"不包含ObjectFactory.class或jaxb.index]

我已经尝试过使用和不使用ClassLoader对象,但都没有成功。如何以这种方式序列化泛型类型?
感谢您的时间。
4个回答

4

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

JAXBContext jaxbContext = JAXBContext.newInstance(name);

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

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

您可能想参考Oracle JAXB教程并注意以下代码行:

import primer.po.*;

...

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

请参考此Javadoc,注意,在需要反序列化的类分布在多个包中时,应传递由冒号分隔的包名列表,例如:--
JAXBContext.newInstance( "com.acme.foo:com.acme.bar" ) 

如果必须传递类名而不是包名,请认真阅读此Javadoc。请注意,JAXBContext实例仅会使用作为参数传递的类以及从这些类静态可达的类进行初始化。建议以编译时已知传递的类名的方式编写程序。
另外,您可能需要注意Java中的泛型与C#中的泛型(特别是在类型擦除方面)有所不同--请参见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


1
感谢您的时间。您的回答非常有帮助。 - MoonKnight

2
您正在使用这个newInstance方法

参数:

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

  • 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>

2

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

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


0

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

public interface IXmlSerializableObject {
    String getSerializedFilePath();
}

其中最关键的是

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

关键属性是 Class<T> 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接口。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接