我定义了以下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
课程很简单,Dog
和DogType
见下文或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.factoryClass
和factoryMethod
。这在这里不起作用,因为我不想将我的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 + "]";
}
}
Dog
,DogType
的包装元素类:
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());
}
}
答案 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
类,因此有几个原因:
@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 强>
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进行调整。