web-dev-qa-db-fra.com

Pourquoi JAXB a-t-il besoin d'un constructeur sans argument pour le marshalling?

Si vous essayez de marshaler une classe qui fait référence à un type complexe qui n'a pas de constructeur no-arg, tel que:

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
}

avec l'implémentation JAXB faisant partie de Java, comme suit:

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

JAXB va lancer un 

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

Maintenant, je comprends pourquoi JAXB a besoin d’un constructeur no-arg pour unmarshalling, car il doit instancier l’objet. Mais pourquoi JAXB a-t-il besoin d'un constructeur sans argument lors du marshalling?

En outre, pourquoi l'implémentation JAXB de Java lève-t-elle une exception si le champ est null et ne sera pas organisée de toute façon?

Est-ce que je manque quelque chose ou s'agit-il simplement de mauvais choix d'implémentation dans l'implémentation JAXB de Java?

43
rouble

Quand une implémentation de JAXB (JSR-222) / initialise ses métadonnées, elle s'assure qu'elle peut prendre en charge à la fois le marshalling et l'unmarshalling. 

Pour les classes POJO qui n'ont pas de constructeur no-arg, vous pouvez utiliser un niveau de type XmlAdapter pour le gérer:

Java.sql.Date n'est pas pris en charge par défaut (bien que dans EclipseLink JAXB (MOXy) il l'est). Ceci peut également être géré en utilisant une variable XmlAdapter spécifiée via @XmlJavaTypeAdapter au niveau du champ, de la propriété ou du package:


En outre, pourquoi l’implémentation JAXB de Java génère-t-elle un exception si le champ est null, et ne sera pas organisé en tous cas?

Quelle exception voyez-vous? Normalement, lorsqu'un champ a la valeur null, il n'est pas inclus dans le résultat XML, sauf s'il est annoté avec @XmlElement(nillable=true), auquel cas l'élément inclura xsi:nil="true"


METTRE À JOUR

Vous pouvez faire ce qui suit:

SqlDateAdapter

Vous trouverez ci-dessous une XmlAdapter qui convertira le Java.sql.Date que votre implémentation JAXB ne sait pas comment gérer en un Java.util.Date, ce qui est le cas:

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

La XmlAdapter est enregistrée via l'annotation @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
}
24
Blaise Doughan

Pour répondre à votre question: je pense qu’il s’agit simplement d’une conception médiocre dans JAXB (ou peut-être dans les implémentations JAXB). L'existence d'un constructeur no-arg est validée lors de la création de la variable JAXBContext et s'applique donc indépendamment du fait que vous souhaitiez utiliser JAXB pour le marshalling ou l'unmarshalling. Il aurait été intéressant que JAXB reporte ce type de vérification sur JAXBContext.createUnmarshaller(). Je pense qu'il serait intéressant de creuser si cette conception est en fait mandatée par la spécification ou s'il s'agit d'une conception d'implémentation dans JAXB-RI.

Mais il existe effectivement une solution de contournement.

JAXB n’a pas besoin d’un constructeur sans argument pour le marshalling. Dans ce qui suit, je supposerai que vous utilisez JAXB uniquement à des fins de marshalling, et non de manière incontrôlée. Je suppose également que vous avez le contrôle sur l'objet immuable que vous voulez marshall pour pouvoir le changer. Si ce n'est pas le cas, le seul moyen d'avancer est XmlAdapter comme décrit dans d'autres réponses.

Supposons que vous ayez une classe, Customer, qui est un objet immuable. L'instanciation s'effectue via un modèle de générateur ou des méthodes statiques.

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

Certes, par défaut, JAXB ne peut pas supprimer un tel objet. Vous obtiendrez l'erreur ".... Le client n'a pas de constructeur par défaut sans argument} _".

Il y a au moins deux façons de résoudre ce problème. Ils s’appuient tous les deux sur la mise en place d’une méthode ou d’un constructeur uniquement pour rendre heureuse l’introspection de JAXB. 

Solution 1

Dans cette méthode, nous indiquons à JAXB qu’il existe une méthode de fabrique statique qu’elle peut utiliser pour instancier une instance de la classe. Nous savons, mais JAXB ne le sait pas, que cela ne sera jamais utilisé. Le truc est l'annotation @XmlType AVEC factoryMethod paramètre. Voici comment:

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

    ...
}

Peu importe si la méthode est privée comme dans l'exemple. JAXB l'acceptera quand même. Votre IDE marquera la méthode comme étant inutilisée si vous la rendez privée, mais je préfère quand même privé.

Solution 2

Dans cette solution, nous ajoutons un constructeur privé sans argument qui passe simplement null au constructeur réel.

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

    ...
}

Peu importe si le constructeur est privé comme dans l'exemple. JAXB l'acceptera quand même. 

Résumé

Les deux solutions répondent au souhait de JAXB d’instanciation sans argument. C’est dommage que nous devions faire cela, lorsque nous savons par nous-mêmes que nous n’avons besoin que de rassembler, et non de mausoler.

Je dois admettre que je ne sais pas dans quelle mesure il s'agit d'un hack qui fonctionnera uniquement avec JAXB-RI et non par exemple avec EclipseLink MOXy. Cela fonctionne vraiment avec JAXB-RI.

2
peterh

Vous semblez avoir l’impression que le code d’introspection JAXB comportera des chemins d’action spécifiques pour l’initialisation. Si tel est le cas, il en résulterait beaucoup de code en double et une implémentation médiocre. J'imagine que le code JAXB a une routine commune qui examine une classe de modèle dès la première utilisation et confirme qu'elle respecte toutes les conventions nécessaires. dans cette situation, il échoue car l'un des membres ne dispose pas du constructeur sans argument requis. la logique d'initialisation est très probablement non spécifique à marshall/unmarshall et également hautement improbable de prendre en compte l'instance d'objet actuelle.

0
jtahlborn