为什么JAXB需要一个no arg构造函数来进行编组?

时间:2012-02-13 21:10:31

标签: java xml jaxb marshalling

如果你试图编组一个引用没有no-arg构造函数的复杂类型的类,例如:

import java.sql.Date;

@XmlRootElement(name = "Foo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
    int i;
    Date d; //java.sql.Date does not have a no-arg constructor
}

使用作为Java一部分的JAXB实现,如下所示:

    Foo foo = new Foo();
    JAXBContext jc = JAXBContext.newInstance(Foo.class);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    Marshaller marshaller = jc.createMarshaller();
    marshaller.marshal(foo, baos);

JAXB会抛出一个

com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions java.sql.Date does not have a no-arg default constructor

现在,我理解为什么JAXB在解组时需要一个无参数的构造函数 - 因为它需要实例化对象。但是为什么JAXB在编组时需要一个无参数的构造函数?

另外,另外一个例子,为什么Java的JAXB实现会在字段为空时抛出异常,并且无论如何都不会被编组?

我是否遗漏了某些内容,或者这些只是Java JAXB实现中的错误实现选择?

4 个答案:

答案 0 :(得分:24)

JAXB (JSR-222)实现初始化其元数据时,它确保它可以支持编组和解组。

对于没有no-arg构造函数的POJO类,您可以使用类型级XmlAdapter来处理它:

默认情况下不支持

java.sql.Date(尽管在EclipseLink JAXB (MOXy)中)。这也可以使用XmlAdapter在字段,属性或包级别通过@XmlJavaTypeAdapter进行处理:


  

另外,另一个人,为什么Java的JAXB实现会抛出   如果该字段为null,则不会被编组   呢?

你看到了什么例外?通常,当字段为空时,它不包含在XML结果中,除非使用@XmlElement(nillable=true)进行注释,在这种情况下,元素将包含xsi:nil="true"


<强>更新

您可以执行以下操作:

<强> SqlDateAdapter

下面是一个XmlAdapter,它会将您的JAXB实现不知道如何处理的java.sql.Date转换为它所执行的java.util.Date

package forum9268074;

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

public class SqlDateAdapter extends XmlAdapter<java.util.Date, java.sql.Date> {

    @Override
    public java.util.Date marshal(java.sql.Date sqlDate) throws Exception {
        if(null == sqlDate) {
            return null;
        }
        return new java.util.Date(sqlDate.getTime());
    }

    @Override
    public java.sql.Date unmarshal(java.util.Date utilDate) throws Exception {
        if(null == utilDate) {
            return null;
        }
        return new java.sql.Date(utilDate.getTime());
    }

}

<强>富

XmlAdapter通过@XmlJavaTypeAdapter注释注册:

package forum9268074;

import java.sql.Date;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement(name = "Foo")
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
    int i;

    @XmlJavaTypeAdapter(SqlDateAdapter.class)
    Date d; //java.sql.Date does not have a no-arg constructor
}

答案 1 :(得分:3)

要回答您的问题:我认为这只是JAXB(或JAXB实现中)的不良设计。在创建JAXBContext时会验证no-arg构造函数的存在,因此无论您想使用JAXB进行编组还是反编组,都适用。如果JAXB将这种类型的检查推迟到JAXBContext.createUnmarshaller(),那就太好了。我认为,深入研究该设计是否真正由规范强制实施,或者它是否是JAXB-RI中的实现设计,将会很有趣。

但是确实有解决方法。

JAXB实际上不需要用于编组的无参数构造函数。在下面的内容中,我假设您仅将JAXB用于编组而不是编组。我还假定您可以控制要编组的不可变对象,以便可以对其进行更改。如果不是这种情况,那么前进的唯一途径是XmlAdapter,如其他答案所述。

假设您有一个类Customer,它是一个不可变的对象。通过Builder模式或静态方法进行实例化。

public class Customer {

    private final String firstName;
    private final String lastName;

    private Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Object created via builder pattern
    public static CustomerBuilder createBuilder() {
        ...
    }

    // getters here ...
}

是的,默认情况下,您无法使JAXB解组此类对象。您将收到错误“ ....客户没有无参数的默认构造函数”。

至少有两种方法可以解决此问题。它们都依赖于仅使用方法或构造函数来使JAXB的自省快乐。

解决方案1 ​​

在此方法中,我们告诉JAXB,有一个静态工厂方法可用于实例化该类的实例。我们知道,但是JAXB并不知道,这确实将永远不会被使用。诀窍是@XmlType annotation with factoryMethod参数。方法如下:

@XmlType(factoryMethod="createInstanceJAXB")
public class Customer {
    ...

    private static CustomerImpl createInstanceJAXB() {  // makes JAXB happy, will never be invoked
        return null;  // ...therefore it doesn't matter what it returns
    }

    ...
}

如示例中所述方法是否私有并不重要。 JAXB仍然会接受。如果将其设为私有,您的IDE会将其标记为未使用,但我仍然更喜欢私有。

解决方案2

在此解决方案中,我们添加了一个私有的无参数构造函数,该构造函数将null传递给实际的构造函数。

public class Customer {
    ...
    private Customer() {  // makes JAXB happy, will never be invoked
        this(null, null);   // ...therefore it doesn't matter what it creates
    }

    ...
}

构造函数是否为私有无关紧要,如示例中所示。 JAXB仍然会接受。

摘要

两个解决方案都满足JAXB对无参数实例化的需求。当我们自己知道我们只需要编组而不是解组时,我们就必须这样做,真是可惜。

我必须承认,我不知道这是一种仅在JAXB-RI上有效而在EclipseLink MOXy上无效的黑客。它肯定适用于JAXB-RI。

答案 2 :(得分:0)

您似乎认为JAXB内省代码将具有特定于操作的初始化路径。如果是这样,那将导致大量重复的代码,并且将是一个糟糕的实现。我想象JAXB代码有一个通用的例程,它在第一次需要时检查一个模型类,并验证它遵循所有必要的约定。在这种情况下,它失败了,因为其中一个成员没有所需的no-arg构造函数。初始化逻辑很可能不是特定的marshall / unmarshall,也不太可能考虑当前的对象实例。

答案 3 :(得分:-2)

某些企业和Dependency Injection框架使用反射Class.newInstance()来创建类的新实例。此方法需要公共无参数构造函数才能实例化对象。