首页 文章

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

提问于
浏览
43

如果您尝试编组一个引用没有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
}

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

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

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

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

    默认情况下不支持 java.sql.Date (尽管在EclipseLink JAXB (MOXy)中) . 这也可以使用在字段,属性或包级别通过 @XmlJavaTypeAdapter 指定的 XmlAdapter 来处理:


    另外,为什么Java的JAXB实现如果该字段为空则抛出异常,并且无论如何都不会被编组?

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


    UPDATE

    您可以执行以下操作:

    SqlDateAdapter

    下面是一个 XmlAdapter ,它将从 java.sql.Date 转换为您的JAXB实现不知道如何处理它所执行的 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());
        }
    
    }
    

    Foo

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

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

  • -2

    回答你的问题:我认为这只是JAXB中的糟糕设计(或者可能是在JAXB实现中) . 在创建 JAXBContext 期间,无arg构造函数的存在会得到验证,因此无论您是否要使用JAXB进行编组或解组,都会适用 . 如果JAXB将这种类型的支票推迟到 JAXBContext.createUnmarshaller() ,那就太棒了 . 我认为,如果这个设计实际上是由规范强制要求,或者它是否是JAXB-RI中的实现设计,那将会很有趣 .

    但确实存在一种解决方法 .

    JAXB不会假设您仅使用JAXB进行编组,而不是解组 . 我还假设您可以控制要编组的不可变对象,以便您可以更改它 . 如果情况并非如此,那么前进的唯一方法就是XmlAdapter,如其他答案中所述 .

    假设您有一个类 Customer ,它是一个不可变对象 . 实例化是通过Builder Pattern或静态方法实现的 .

    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解组这样的对象 . 你会得到错误“....客户没有no-arg默认构造函数” .

    至少有两种方法可以解决这个问题 . 他们都依赖于单独使用方法或构造函数来使JAXB的内省快乐 .

    Solution 1

    在这个方法中,我们告诉JAXB有's a static factory method it can use to instantiate an instance of the class. We know, but JAXB doesn' t,确实永远不会使用它 . 诀窍是@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会将该方法标记为未使用,但我仍然更喜欢私有 .

    Solution 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仍然会接受它 .

    Summary

    两种解决方案都满足JAXB对无参数实例化的需求 . 当我们自己知道时,我们需要做到这一点,这是一种耻辱我们只需要编组,而不是解组 .

    我不得不承认,我不知道这种黑客程度只适用于JAXB-RI,而不适用于EclipseLink MOXy . 它肯定适用于JAXB-RI .

  • 0

    某些企业和Dependency Injection框架使用反射Class.newInstance()来创建类的新实例 . 此方法要求public no-arg构造函数能够实例化该对象 .

相关问题