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

发布于 2025-01-05 00:17:36 字数 943 浏览 2 评论 0原文

如果您尝试封送一个引用没有无参数构造函数的复杂类型的类,例如:

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 将抛出 a

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 实现中的错误实现选择?

If you try to marshal a class which references a complex type that does not have a no-arg constructor, such as:

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
}

with the JAXB implementation that is part of Java, as follows:

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

JAXB will throw a

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

Now, I understand why JAXB needs a no-arg constructor on unmarshalling - because it needs to instantiate the object. But why does JAXB need a no-arg constructor while marshalling?

Also, another nit, why does Java's JAXB implementation throw an exception if the field is null, and isn't going to be marshalled anyway?

Am I missing something or are these just bad implementation choices in Java's JAXB implementation?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

一影成城 2025-01-12 00:17:36

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

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

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


另外,还有一个问题,为什么 Java 的 JAXB 实现会抛出一个错误
如果该字段为空并且不会被编组,则会出现异常
无论如何?

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


更新

您可以执行以下操作:

SqlDateAdapter

下面是一个将从java.sql.Date转换而来的XmlAdapter您的 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
}

When a JAXB (JSR-222) implementation initializes its metadata it ensures that it can support both marshalling and unmarshalling.

For POJO classes that do not have a no-arg constructor you can use a type level XmlAdapter to handle it:

java.sql.Date is not supported by default (although in EclipseLink JAXB (MOXy) it is). This can also be handled using an XmlAdapter specified via @XmlJavaTypeAdapter at field, property, or package level:


Also, another nit, why does Java's JAXB implementation throw an
exception if the field is null, and isn't going to be marshalled
anyway?

What exception are you seeing? Normally when a field is null it is not included in the XML result, unless it is annotated with @XmlElement(nillable=true) in which case the element will include xsi:nil="true".


UPDATE

You could do the following:

SqlDateAdapter

Below is an XmlAdapter that will convert from the java.sql.Date that your JAXB implementation doesn't know how to handle to a java.util.Date which it does:

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

The XmlAdapter is registered via the @XmlJavaTypeAdapter annotation:

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
}
生死何惧 2025-01-12 00:17:36

回答你的问题:我认为这只是 JAXB 中的糟糕设计(或者可能是 JAXB 实现中的设计)。无参数构造函数的存在会在创建 JAXBContext 期间得到验证,因此无论您想使用 JAXB 进行编组还是解组,都适用。如果 JAXB 将这种类型的检查推迟到 JAXBContext.createUnmarshaller() 那就太好了。我认为深入研究这个设计是否确实是规范所强制的,或者它是否是 JAXB-RI 中的实现设计,将会很有趣。

但确实有一个解决方法。

JAXB 实际上不需要无参数构造函数来进行编组。下面我假设您仅使用 JAXB 进行编组,而不是解组。我还假设您可以控制要编组的不可变对象,以便可以更改它。如果情况并非如此,那么唯一的前进方法是 XmlAdapter 如其他答案中所述。

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

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 不知道,这确实永远不会被使用。诀窍是 带有factoryMethod参数的@XmlType注释。方法如下:

@XmlType(factoryMethod="createInstanceJAXB")
public class Customer {
    ...
    
    private static Customer createInstanceJAXB() {  // makes JAXB happy, will never be invoked
        throw new UnsupportedOperationException("Unexpected invocation: Customer object is not intended to be used for unmarshalling, only marshalling");
    }

    ...
}

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

解决方案 2

在此解决方案中,我们添加一个私有无参数构造函数,该构造函数仅将 null 传递到真正的构造函数中。

public class Customer {
    ...
    private Customer() {  // makes JAXB happy, will never be invoked
        throw new UnsupportedOperationException("Unexpected invocation: Customer object is not intended to be used for unmarshalling, only marshalling");
    }

    ...
}

构造函数是否像示例中那样是私有的并不重要。 JAXB 仍将接受它。

总结

两种解决方案都满足了 JAXB 对无参数实例化的需求。遗憾的是,当我们自己知道我们只需要编组而不是解组时,我们需要这样做。

我不得不承认,我不知道这种黑客攻击在多大程度上只能与 JAXB-RI 一起使用,而不能与 EclipseLink MOXy 一起使用。它绝对可以与 JAXB-RI 配合使用。

To answer your question: I think this is just poor design in JAXB (or perhaps in JAXB implementations). The existence of a no-arg constructor gets validated during creation of the JAXBContext and therefore applies regardless if you want to use JAXB for marshalling or unmarshalling. It would have been great if JAXB would defer this type of check to JAXBContext.createUnmarshaller(). I think it would be interesting to dig into if this design is actually mandated by the spec or if it is an implementation design in JAXB-RI.

But there's indeed a workaround.

JAXB doesn't actually need a no-arg constructor for marshalling. In the following I'll assume you are using JAXB solely for marshalling, not unmarshalling. I also assume that you have control over the immutable object which you want to marshall so that you can change it. If this is not the case then the only way forward is XmlAdapter as described in other answers.

Suppose you have a class, Customer, which is an immutable object. Instantiation is via Builder Pattern or static methods.

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 ...
}

True, that by default you cannot get JAXB to unmarshall such an object. You'll get error "....Customer does not have a no-arg default constructor".

There are at least two ways of solving this. They both rely on putting in a method or constructor solely to make JAXB's introspection happy.

Solution 1

In this method we tell JAXB that there's a static factory method it can use to instantiate an instance of the class. We know, but JAXB doesn't, that indeed this will never be used. The trick is the @XmlType annotation with factoryMethod parameter. Here's how:

@XmlType(factoryMethod="createInstanceJAXB")
public class Customer {
    ...
    
    private static Customer createInstanceJAXB() {  // makes JAXB happy, will never be invoked
        throw new UnsupportedOperationException("Unexpected invocation: Customer object is not intended to be used for unmarshalling, only marshalling");
    }

    ...
}

It doesn't matter if the method is private as in the example. JAXB will still accept it. Your IDE will flag the method as unused if you make it private, but I still prefer private.

Solution 2

In this solution we add a private no-arg constructor which just passes null into the real constructor.

public class Customer {
    ...
    private Customer() {  // makes JAXB happy, will never be invoked
        throw new UnsupportedOperationException("Unexpected invocation: Customer object is not intended to be used for unmarshalling, only marshalling");
    }

    ...
}

It doesn't matter if the constructor is private as in the example. JAXB will still accept it.

Summary

Both solutions satisfies JAXB's desire for no-arg instantiation. It is a shame that we need to do this, when we know by ourselves that we only need to marshal, not unmarshal.

I have to admit that I do not know to what extent this is a hack that will only work with JAXB-RI and not for example with EclipseLink MOXy. It definitely works with JAXB-RI.

允世 2025-01-12 00:17:36

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

You seem to be under the impression that the JAXB introspection code will have action specific paths for initialization. if so, that would result in a lot of duplicate code and would be a poor implementation. i would imagine that the JAXB code has a common routine which examines a model class the first time it is needed and validates that it follows all the necessary conventions. in this situation, it is failing because one of the members does not have the required no-arg constructor. the initialization logic is most likely not marshall/unmarshall specific and also highly unlikely to take the current object instance into account.

酒解孤独 2025-01-12 00:17:36

一些企业和依赖注入框架使用反射Class.newInstance() 创建类的新实例。此方法需要一个公共无参数构造函数才能实例化该对象。

Some enterprise and Dependency Injection frameworks use reflection Class.newInstance() to create a new instance of your classes. This method requires a public no-arg constructor to be able to instantiate the object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文