webentwicklung-frage-antwort-db.com.de

Warum braucht JAXB für das Marshalling keinen Arg-Konstruktor?

Wenn Sie versuchen, eine Klasse zu marshallen, die auf einen komplexen Typ verweist, der keinen No-Arg-Konstruktor enthält, z.

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
}

mit der JAXB-Implementierung, die Teil von Java ist, wie folgt:

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

JAXB wirft ein 

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

Jetzt verstehe ich, warum JAXB einen No-Arg-Konstruktor für das Unmarshalling benötigt - weil er das Objekt instanziieren muss. Aber warum braucht JAXB beim Rangieren einen No-Arg-Konstruktor?

Außerdem, warum, löst die JAXB-Implementierung von Java eine Ausnahme aus, wenn das Feld null ist und nicht sowieso gemarshallt wird?

Fehle ich etwas oder sind das nur schlechte Implementierungsentscheidungen in Javas JAXB-Implementierung?

43
rouble

Wenn eine Implementierung von JAXB (JSR-222) ihre Metadaten initialisiert, wird sichergestellt, dass sie sowohl das Marshalling als auch das De-Marshalling unterstützt. 

Für POJO-Klassen, die keinen no-arg-Konstruktor haben, können Sie eine Typebene XmlAdapter verwenden, um damit umzugehen:

Java.sql.Date wird standardmäßig nicht unterstützt (obwohl in EclipseLink JAXB (MOXy) ist). Dies kann auch mit einer XmlAdapter behandelt werden, die über @XmlJavaTypeAdapter auf Feld-, Eigenschafts- oder Paketebene angegeben wird:


Außerdem, warum, wirft Javas JAXB-Implementierung eine Ausnahme, wenn das Feld NULL ist und nicht gemarshallt wird sowieso?

Welche Ausnahme sehen Sie? Normalerweise ist ein Feld, wenn es null ist, nicht im XML-Ergebnis enthalten, es sei denn, es ist mit @XmlElement(nillable=true) versehen. In diesem Fall enthält das Element xsi:nil="true"


UPDATE

Sie könnten folgendes tun:

SqlDateAdapter

Nachfolgend finden Sie eine XmlAdapter, die aus dem Java.sql.Date, den Ihre JAXB-Implementierung nicht kennt, in einen Java.util.Date umzuwandeln ist.

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

Die Variable XmlAdapter wird über die Annotation @XmlJavaTypeAdapter registriert:

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
}
24
Blaise Doughan

Um Ihre Frage zu beantworten: Ich denke, das ist nur ein schlechtes Design in JAXB (oder vielleicht in JAXB-Implementierungen). Das Vorhandensein eines No-Arg-Konstruktors wird beim Erstellen der Variable JAXBContext überprüft und gilt daher unabhängig davon, ob Sie JAXB für das Marshalling oder das Aufheben von Marshalls verwenden möchten. Es wäre schön gewesen, wenn JAXB diese Art der Überprüfung auf JAXBContext.createUnmarshaller() verschieben würde. Ich denke, es wäre interessant, sich mit Dig auseinanderzusetzen, ob dieses Design tatsächlich von der Spezifikation vorgegeben wird oder ob es sich um ein Implementierungsdesign in JAXB-RI handelt.

Es gibt jedoch einen Workaround.

JAXB benötigt eigentlich keinen No-Arg-Konstruktor für das Marshalling. Im Folgenden gehe ich davon aus, dass Sie JAXB ausschließlich zum Rangieren und nicht zum Rangieren verwenden. Ich gehe außerdem davon aus, dass Sie die Kontrolle über das unveränderliche Objekt haben, das Sie zusammenstellen möchten, damit Sie es ändern können. Wenn dies nicht der Fall ist, ist der einzige Weg nach vorne XmlAdapter , wie in anderen Antworten beschrieben.

Angenommen, Sie haben eine Klasse, Customer, die ein unveränderliches Objekt ist. Die Instantiierung erfolgt über Builder Pattern oder statische Methoden.

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

Es stimmt, dass Sie standardmäßig JAXB nicht dazu auffordern können, ein solches Objekt zu entfernen. Sie erhalten die Fehlermeldung ".... Der Kunde hat keinen Standardkonstruktor no-arg".

Es gibt mindestens zwei Möglichkeiten, dies zu lösen. Beide sind darauf angewiesen, eine Methode oder einen Konstruktor einzubauen, um die Selbstbeobachtung von JAXB glücklich zu machen. 

Lösung 1

In dieser Methode teilen wir JAXB mit, dass es eine statische Factory-Methode gibt, mit der eine Instanz der Klasse instanziiert werden kann. Wir wissen, aber JAXB weiß nicht, dass dies niemals verwendet wird. Der Trick ist der @XmlType ANNOTATION WITH factoryMethod Parameter. Hier ist wie:

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

    ...
}

Es ist egal, ob die Methode wie im Beispiel privat ist. JAXB wird es immer noch akzeptieren. Ihre IDE kennzeichnet die Methode als nicht verwendet, wenn Sie sie als privat definieren, aber ich bevorzuge privat.

Lösung 2

In dieser Lösung fügen wir einen privaten no-arg-Konstruktor hinzu, der einfach null an den echten Konstruktor übergibt.

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

    ...
}

Es ist egal, ob der Konstruktor wie im Beispiel privat ist. JAXB wird es immer noch akzeptieren. 

Zusammenfassung

Beide Lösungen erfüllen den Wunsch von JAXB nach einer Instantiierung ohne Argumente. Es ist eine Schande, dass wir dies tun müssen, wenn wir selbst wissen, dass wir nur Marshalling betreiben müssen, nicht Unmarschall.

Ich muss zugeben, dass ich nicht weiß, in welchem ​​Umfang dies ein Hack ist, der nur mit JAXB-RI und nicht beispielsweise mit EclipseLink MOXy funktioniert. Es funktioniert definitiv mit JAXB-RI.

2
peterh

Sie haben scheinbar den Eindruck, dass der JAXB-Introspektionscode aktionsspezifische Pfade für die Initialisierung hat. Wenn dies der Fall ist, führt dies zu einer Vielzahl von doppeltem Code und wäre eine schlechte Implementierung. Ich könnte mir vorstellen, dass der JAXB-Code eine gemeinsame Routine hat, die eine Modellklasse beim ersten Mal untersucht und bestätigt, dass sie allen notwendigen Konventionen folgt. In dieser Situation schlägt es fehl, weil eines der Member nicht den erforderlichen No-Arg-Konstruktor hat. Die Initialisierungslogik ist höchstwahrscheinlich nicht spezifisch für Marshalls/Unmarshalls und es ist auch höchst unwahrscheinlich, dass sie die aktuelle Objektinstanz berücksichtigt.

0
jtahlborn