簡體   English   中英

為什么 JAXB 需要一個無參數構造函數來進行編組?

[英]Why does JAXB need a no arg constructor for marshalling?

如果您嘗試封送引用沒有無參數構造函數的復雜類型的類,例如:

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 實現中的錯誤實現選擇?

JAXB (JSR-222)實現初始化其元數據時,它確保它可以支持編組和解組。

對於沒有無參數構造函數的 POJO 類,您可以使用類型級別的XmlAdapter來處理它:

默認情況下不支持java.sql.Date (盡管在EclipseLink JAXB (MOXy)中支持)。 這也可以使用通過@XmlJavaTypeAdapter在字段、屬性或包級別指定的XmlAdapter來處理:


另外,另一個問題,如果字段為空,為什么 Java 的 JAXB 實現會拋出異常,並且無論如何都不會被編組?

你看到了什么異常? 通常,當一個字段為 null 時,它不包含在 XML 結果中,除非它用@XmlElement(nillable=true)注釋,在這種情況下元素將包含xsi:nil="true"


更新

您可以執行以下操作:

日期適配器

下面是一個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
}

回答您的問題:我認為這只是 JAXB(或者可能在 JAXB 實現中)中的糟糕設計。 無參數構造函數的存在在創建JAXBContext期間得到驗證,因此無論您是想使用 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 解組這樣的對象。 您將收到錯誤“ ....客戶沒有無參數的默認構造函數”。

至少有兩種方法可以解決這個問題。 它們都依賴於只放入一個方法或構造函數來使 JAXB 的自省愉快。

解決方案1

在這個方法中,我們告訴 JAXB 有一個靜態工廠方法可以用來實例化類的實例。 我們知道,但 JAXB 不知道,這確實永遠不會被使用。 訣竅是帶有factoryMethod參數的@XmlType注釋 就是這樣:

@XmlType(factoryMethod="createInstanceJAXB")
public class Customer {
    ...
    
    private static Customer 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 的 hack。 它絕對適用於 JAXB-RI。

您似乎認為 JAXB 內省代碼將具有用於初始化的特定於操作的路徑。 如果是這樣,那將導致大量重復代碼並且將是一個糟糕的實現。 我想 JAXB 代碼有一個通用例程,它在第一次需要模型類時檢查它並驗證它是否遵循所有必要的約定。 在這種情況下,它會失敗,因為其中一個成員沒有所需的無參數構造函數。 初始化邏輯很可能不是特定於編組/解組的,也極不可能考慮當前對象實例。

一些企業和依賴注入框架使用反射Class.newInstance()來創建類的新實例。 此方法需要公共無參數構造函數才能實例化對象。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM