java - 为什么在解码期间不使用 ObjectFactory?

标签 java xml jaxb

我定义了以下 ObjectFactory:

@XmlRegistry
public class ObjectFactory {

    public Dogs createDogs() {
        return new Dogs();
    }

    @XmlElementDecl(name = "dog")
    public Dog createDog(DogType value) {
        return new Dog(value);
    }

    @XmlElementDecl(name = "fido", substitutionHeadName = "dog", substitutionHeadNamespace = "")
    public Dog createFido(DogType value) {
        return new Dog("fido", value);
    }

    @XmlElementDecl(name = "barks", substitutionHeadName = "dog", substitutionHeadNamespace = "")
    public Dog createBarks(DogType value) {
        return new Dog("barks", value);
    }
}

(Dogs 类很简单,DogDogType 见下文或 here。)

我正在解码以下 XML:

<listOfDogs>
    <dogs>
        <dog>
            <name>henry</name>
            <sound>bark</sound>
        </dog>
        <fido>
            <sound>woof</sound>
        </fido>
        <barks>
            <sound>miau</sound>
        </barks>
    </dogs>
</listOfDogs>

我真诚地期待 JAXB 会在解码期间调用我的 createFido(...)createBarks(...) 方法。但这不会发生。 Dog 构造函数是通过反射直接调用的,不使用适当的 create... 方法。

我的问题是:

为什么在解码期间不调用 ObjectFactory

不应该吗?或者它只是一个用来保存 @XmlRegistry/@XmlElementDecl 声明的虚拟对象?

我也查过这个问题:

What is the ObjectFactory role during JAXB-Unmarshalling?

解决方案是使用@XmlType.factoryClassfactoryMethod。这在这里不起作用,因为我不想将我的 DogType 静态链接到特定的实例化例程。我希望它在运行时根据元素名称来决定。我的目标是根据元素名称实例化相同但不同的类。


现在一些代码来完成它。

根元素类:

@XmlRootElement(name = "listOfDogs")
public class Dogs {

    private List<JAXBElement<DogType>> dogs = new LinkedList<JAXBElement<DogType>>();

    @XmlElementWrapper(name = "dogs")
    @XmlElementRef(name = "dog")
    public List<JAXBElement<DogType>> getDogs() {
        return this.dogs;
    }

    @Override
    public String toString() {
        return "Dogs [dogs=" + dogs + "]";
    }
}

DogDogType 的包装元素类:

public class Dog extends JAXBElement<DogType> {

    public static final QName NAME = new QName("dog");

    private static final long serialVersionUID = 1L;

    public Dog(DogType value) {
        super(NAME, DogType.class, value);
    }

    public Dog(String dogName, DogType value) {
        super(NAME, DogType.class, value);
    }

    @Override
    public QName getName() {
        final DogType value = getValue();
        if (value != null && value.getName() != null) {
            return new QName(value.getName());
        } else {
            return super.getName();
        }
    }
}

狗类型:

public class DogType {

    private String name;
    private String sound;

    public String getName() {
        return name;
    }

    public void setName(String dogName) {
        this.name = dogName;
    }

    public String getSound() {
        return sound;
    }

    public void setSound(String sound) {
        this.sound = sound;
    }
}

测试:

public class DogTest {

    @Test
    public void unmarshallsDogs() throws JAXBException {
        final JAXBContext context = JAXBContext
                .newInstance(ObjectFactory.class);
        final Dogs dogs = (Dogs) context.createUnmarshaller().unmarshal(
                getClass().getResource("dogs.xml"));
        Assert.assertEquals(3, dogs.getDogs().size());
        // Does not work
//      Assert.assertEquals("henry", dogs.getDogs().get(0).getValue()
//              .getName());
        Assert.assertEquals("bark", dogs.getDogs().get(0).getValue().getSound());
        // Does not work
//      Assert.assertEquals("fido", dogs.getDogs().get(1).getValue()
//              .getName());
        Assert.assertEquals("woof", dogs.getDogs().get(1).getValue().getSound());
        // Does not work
//      Assert.assertEquals("barks", dogs.getDogs().get(2).getValue()
//              .getName());
        Assert.assertEquals("miau", dogs.getDogs().get(2).getValue().getSound());
    }
}

代码也可以在 GitHub 上找到 herehere .

最佳答案

简短的回答是因为工厂方法没有生成到 @XmlType 注释中以告诉 JAXB 这样做:

@XmlRootElement(name = "listOfDogs")
@XmlType(factoryClass=ObjectFactory.class, factoryMethod="createDogs") // not generated
public class Dogs {

Shouldn't it be? Or is ist just a dummy to hold the @XmlRegistry/@XmlElementDecl declarations?

在我看来是的,它应该用于实例化类。

ObjectFactory 是对 JAXB 1.0 的倒退。在 JAXB 1.0 中,规范定义了生成的接口(interface)的外观,并且实现可以使用它们想要提供的任何 impl 来支持这些生成的接口(interface)。那时您需要使用 ObjectFactory 类以独立于供应商的方式创建您的模型。

JAXB 2.0 切换到 POJO 模型,您可以在其中自由使用默认构造函数。如果 JAXB 1.0 从未存在过,是否会有 ObjectFactory 类,这很难说。由于 ObjectFactory 类以前存在,因此保留了几个原因:

  1. 它使从 JAXB 1.0 转换过来的人更容易与生成的模型进行交互。
  2. 它提供了一个位置来通过 @XmlElementDecl 为一个类指定多个根元素。 @XmlRegistry 注释实际上只是一个标记注释,用于指示包含 @XmlElementDecl 注释的类,而不将其限制为名为 ObjectFactory 的类.

您的用例

您的用例可以通过 XmlAdapter 实现,尽管我不清楚您在 ObjectFactory 中尝试拥有什么逻辑。

XmlAdapter (DogAdapter)

您的自定义逻辑在 XmlAdapter 上进行。

import javax.xml.bind.*;
import javax.xml.bind.annotation.adapters.*;

public class DogAdapter extends XmlAdapter<JAXBElement<DogType>, JAXBElement<DogType>> {

    @Override
    public JAXBElement<DogType> unmarshal(JAXBElement<DogType> v) throws Exception {
        return new Dog(v.getName().getLocalPart(), v.getValue());
    }

    @Override
    public JAXBElement<DogType> marshal(JAXBElement<DogType> v) throws Exception {
        return v;
    }

}

XmlAdapter 是从 @XmlJavaTypeAdapter 注释中引用的。

import java.util.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement(name = "listOfDogs")
public class Dogs {

    private List<JAXBElement<DogType>> dogs = new LinkedList<JAXBElement<DogType>>();

    @XmlElementWrapper(name = "dogs")
    @XmlElementRef(name = "dog")
    @XmlJavaTypeAdapter(DogAdapter.class)
    public List<JAXBElement<DogType>> getDogs() {
        return this.dogs;
    }

    @Override
    public String toString() {
        return "Dogs [dogs=" + dogs + "]";
    }

}

对象工厂

ObjectFactory 现在是一个仅包含 @XmlElementDecl 注释的哑类:

import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;

@XmlRegistry
public class ObjectFactory {

    public Dogs createDogs() {
        return new Dogs();
    }

    @XmlElementDecl(name = "dog")
    public JAXBElement<DogType> createDog(DogType value) {
        return new Dog(value);
    }

    @XmlElementDecl(name = "fido", substitutionHeadName = "dog", substitutionHeadNamespace = "")
    public JAXBElement<DogType> createFido(DogType value) {
        return new JAXBElement<DogType>(new QName("fido"), DogType.class, value);
    }

    @XmlElementDecl(name = "barks", substitutionHeadName = "dog", substitutionHeadNamespace = "")
    public JAXBElement<DogType> createBarks(DogType value) {
        return new JAXBElement<DogType>(new QName("barks"), DogType.class, value);
    }

}

更新

My question, however is more about the specification. According to the spec, should the create* methods from the ObjectFactory be executed or not?

在 JAXB 2 中,从头开始创建的模型与从 XML 模式生成的模型没有区别。因此,您需要查看规范中关于类的内容。根据下面的引用,它归结为无参数构造函数或指定的工厂方法。

来自 JAXB 2.2 (JSR-222)8.7.1.2 映射 部分规范:

a class must have a public or protected no-arg constructor or a factory method identified by {factoryClass(), factoryMethod()} unless it is adapted using @XmlJavaTypeAdapter.

关于java - 为什么在解码期间不使用 ObjectFactory?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26439184/

相关文章:

java - 手动编写的 XSD 和生成的 java 类会导致使用 JAXB 出现 UnmarshallException

java - 使用 JDOM 解析时处理特殊字符

c# - 如何将异常对象序列化为 xml 字符串

java - 如何将 PreferenceFragment 与 ViewPager 一起使用?

java - 如何从具有不同字段的同一类创建两个 json 文件

JAXB 生成无效的 Javadoc - @link byte[]

java - Cordova 到 Android studio 错误 malformed\uxxxx 编码

java - 在java中创建多个线程

java - Java 中的条件赋值

java - HandoutMasterPart 默认构造函数总是抛出错误