为什么 JAXB 需要一个无参数构造函数来进行编组?
如果您尝试封送一个引用没有无参数构造函数的复杂类型的类,例如:
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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
当 JAXB (JSR-222) 实现初始化其元数据时,它确保它可以支持编组和解组。
对于没有无参数构造函数的 POJO 类,您可以使用类型级别
XmlAdapter
来处理它:默认情况下不支持
java.sql.Date
(尽管在 EclipseLink JAXB (MOXy) 是的)。这也可以使用通过@XmlJavaTypeAdapter
在字段、属性或包级别指定的XmlAdapter
来处理:您看到什么异常?通常,当字段为 null 时,它不会包含在 XML 结果中,除非使用
@XmlElement(nillable=true)
进行注释,在这种情况下,该元素将包含xsi:nil="true “
。更新
您可以执行以下操作:
SqlDateAdapter
下面是一个将从
java.sql.Date
转换而来的XmlAdapter
您的 JAXB 实现不知道如何处理java.util.Date
,它确实这样做:Foo
XmlAdapter
是通过@XmlJavaTypeAdapter
注解: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 anXmlAdapter
specified via@XmlJavaTypeAdapter
at field, property, or package level: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 includexsi:nil="true"
.UPDATE
You could do the following:
SqlDateAdapter
Below is an
XmlAdapter
that will convert from thejava.sql.Date
that your JAXB implementation doesn't know how to handle to ajava.util.Date
which it does:Foo
The
XmlAdapter
is registered via the@XmlJavaTypeAdapter
annotation:回答你的问题:我认为这只是 JAXB 中的糟糕设计(或者可能是 JAXB 实现中的设计)。无参数构造函数的存在会在创建
JAXBContext
期间得到验证,因此无论您想使用 JAXB 进行编组还是解组,都适用。如果 JAXB 将这种类型的检查推迟到 JAXBContext.createUnmarshaller() 那就太好了。我认为深入研究这个设计是否确实是规范所强制的,或者它是否是 JAXB-RI 中的实现设计,将会很有趣。但确实有一个解决方法。
JAXB 实际上不需要无参数构造函数来进行编组。下面我假设您仅使用 JAXB 进行编组,而不是解组。我还假设您可以控制要编组的不可变对象,以便可以更改它。如果情况并非如此,那么唯一的前进方法是
XmlAdapter
如其他答案中所述。假设您有一个类
Customer
,它是一个不可变对象。实例化是通过构建器模式或静态方法进行的。确实,默认情况下您无法让 JAXB 来解组此类对象。您将收到错误“....客户没有无参数默认构造函数”。
至少有两种方法可以解决这个问题。它们都仅仅依靠放入方法或构造函数来使 JAXB 的自省满意。
解决方案 1
在此方法中,我们告诉 JAXB 有一个静态工厂方法,它可以用来实例化该类的实例。我们知道,但 JAXB 不知道,这确实永远不会被使用。诀窍是
带有
factoryMethod
参数的@XmlType
注释。方法如下:该方法是否像示例中那样是私有的并不重要。 JAXB 仍将接受它。如果您将该方法设为私有,您的 IDE 会将其标记为未使用,但我仍然更喜欢私有。
解决方案 2
在此解决方案中,我们添加一个私有无参数构造函数,该构造函数仅将 null 传递到真正的构造函数中。
构造函数是否像示例中那样是私有的并不重要。 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 toJAXBContext.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.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 withfactoryMethod
parameter. Here's how: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.
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.
您似乎认为 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.
一些企业和依赖注入框架使用反射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.