为什么在解组时不使用ObjectFactory?

时间:2014-10-18 11:20:46

标签: 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();
        }
    }
}

DogType

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上找到。

1 个答案:

答案 0 :(得分:6)

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

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

  

不应该吗?或者只是一个假人来举行   @ XmlRegistry / @ XmlElementDecl声明?

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

ObjectFactory是对JAXB 1.0的回击。在JAXB 1.0中,规范定义了生成的接口看起来像什么,并且实现可以支持那些生成的接口以及它们想要提供的内容。那时你需要使用ObjectFactory类以独立于供应商的方式创建模型。

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

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

  3. 您的用例

    您的用例可能可以使用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

    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);
        }
    
    }
    

    更新

      

    然而,我的问题更多的是关于规范。根据   spec,应该执行ObjectFactory中的create *方法还是   不?

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

    来自JAXB 2.2 (JSR-222)规范的 8.7.1.2 Mapping 部分:

      

    一个类必须有一个public或protected no-arg构造函数或者一个   由{factoryClass(),factoryMethod()}标识的工厂方法,除非   它使用@XmlJavaTypeAdapter进行调整。