web-dev-qa-db-fra.com

Pourquoi JAXB ne génère-t-il pas de setters pour les listes

Lorsque je génère des classes JAXB à partir d'un XSD, les éléments avec maxOccurs="unbounded" obtient une méthode getter générée pour eux, mais aucune méthode setter, comme suit:

/**
 * Gets the value of the element3 property.
 * 
 * <p>
 * This accessor method returns a reference to the live list,
 * not a snapshot. Therefore any modification you make to the
 * returned list will be present inside the JAXB object.
 * This is why there is not a <CODE>set</CODE> method for the element3 property.
 * 
 * <p>
 * For example, to add a new item, do as follows:
 * <pre>
 *    getElement3().add(newItem);
 * </pre>
 * 
 * 
 * <p>
 * Objects of the following type(s) are allowed in the list
 * {@link Type }
 * 
 * 
 */
public List<Type> getElement3() {
    if (element3 == null) {
        element3 = new ArrayList<Type>();
    }
    return this.element3;
}

Le commentaire de la méthode indique clairement comment l'utiliser, mais ma question est la suivante:
Pourquoi JAXB ne génère-t-il pas simplement un setter, en suivant les règles Java Beans? Je sais que je peux écrire la méthode setter moi-même, mais un avantage à l'approche suggérée dans la méthode getter générée?

Voici mon XSD:

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.example.org/DoTransfer/" targetNamespace="http://www.example.org/DoTransfer/">

    <element name="CollectionTest" type="tns:CollectionTest"></element>

    <complexType name="CollectionTest">
        <sequence>
            <element name="element1" type="string" maxOccurs="1" minOccurs="1"></element>
            <element name="element2" type="boolean" maxOccurs="1" minOccurs="1"></element>
            <element name="element3" type="tns:type" maxOccurs="unbounded" minOccurs="1" nillable="true"></element>
        </sequence>
    </complexType>


    <complexType name="type">
        <sequence>
            <element name="subelement1" type="string" maxOccurs="1" minOccurs="1"></element>
            <element name="subelement2" type="string" maxOccurs="1" minOccurs="0"></element>
        </sequence>
    </complexType>
</schema>
55
Ahmad Y. Saleh

Voici la justification de la spécification JAXB - page 60.

Note de conception - Il n'y a pas de méthode de définition pour une propriété List. Le getter renvoie la liste par référence. Un élément peut être ajouté à la liste renvoyée par la méthode getter à l'aide d'une méthode appropriée définie sur Java.util.List. La justification de cette conception dans JAXB 1.0 était de permettre à l'implémentation d'encapsuler la liste et de pouvoir effectuer des vérifications à mesure que le contenu était ajouté ou supprimé de la liste.

Donc, si l'implémentation de la liste était prioritaire sur l'ajout/la suppression pour effectuer la validation, le remplacement de cette liste "spéciale" par (par exemple) une liste de tableaux annulerait ces vérifications.

27
jdessey

Lien pour: Pas de setter pour la liste

Le code de la méthode getter garantit que la liste est créée. Il n'y a pas de setter correspondant ce qui signifie que tous les ajouts ou suppressions d'éléments de liste doivent être effectués sur le "live " liste.

Comme la citation dit qu'il n'y a pas de setter car lorsque vous utilisez la méthode getter, cela garantit qu'une nouvelle instance de la liste est initialisée si elle n'est pas présente.

Et après cela, lorsque vous devez ajouter ou supprimer quoi que ce soit, vous devrez utiliser

getElement3().add(Type);

[~ # ~] mise à jour [~ # ~] : différence de marshaling pour null et liste vide

Exemple où la liste est null

@XmlRootElement(name = "list-demo")
public class ListDemo {

    @XmlElementWrapper(name = "list")
    @XmlElement(name = "list-item")
    private List<String> list;

}

SORTIE sera

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<list-demo/>

Exemple où la liste est vide

@XmlRootElement(name = "list-demo")
public class ListDemo {

    @XmlElementWrapper(name = "list")
    @XmlElement(name = "list-item")
    private List<String> list = new ArrayList<String>();

}

LA SORTIE sera:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<list-demo>
    <list/>
</list-demo>
25
Narendra Pathai

D'accord avec l'inquiétude de Patrick ci-dessus. Si je codais directement pour les classes Java Java directement, je serais heureux d'obliger, mais j'utilise un outil introspectif attend soit un setter soit un membre directement accessible. A réussi avec un plugin à XJC depuis https://github.com/highsource/jaxb2-basics/wiki/JAXB2-Setters-Plugin et en ajoutant un argument -B-Xsetter à wsimport

4
Chris Klopfenstein

On peut écrire leur propre XJC plugin pour leurs besoins spécifiques.

Dans cet exemple, on essaie d'ajouter un champ id de type long dans chaque fichier Java Java à partir de xjc:

package com.ricston;

import Java.io.IOException;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;

import com.Sun.codemodel.JBlock;
import com.Sun.codemodel.JCodeModel;
import com.Sun.codemodel.JFieldVar;
import com.Sun.codemodel.JMethod;
import com.Sun.codemodel.JMod;
import com.Sun.codemodel.JType;
import com.Sun.codemodel.JVar;
import com.Sun.tools.xjc.BadCommandLineException;
import com.Sun.tools.xjc.Options;
import com.Sun.tools.xjc.Plugin;
import com.Sun.tools.xjc.outline.ClassOutline;
import com.Sun.tools.xjc.outline.Outline;

public class XJCPlugin extends Plugin {

  public final static String ID = "id";
    public final static JType LONG_TYPE = new JCodeModel().LONG;
    public final static String ID_GETTER = "getId";
    public final static JType VOID_TYPE = new JCodeModel().VOID;
    public final static String ID_SETTER = "setId";

    @Override
    public String getOptionName() {
        return "Xexample-plugin";
    }

    @Override
    public int parseArgument(Options opt, String[] args, int i)
            throws BadCommandLineException, IOException {
        return 1;
    }

    @Override
    public String getUsage() {
        return "  -Xexample-plugin    :  xjc example plugin";
    }

    @Override
    public boolean run(Outline model, Options opt, ErrorHandler errorHandler)
            throws SAXException {

        for (ClassOutline classOutline : model.getClasses()) {
            JFieldVar globalId = classOutline.implClass.field(JMod.PRIVATE,
                    LONG_TYPE, ID);

            JMethod idGetterMethod = classOutline.implClass.method(JMod.PUBLIC,
                    LONG_TYPE, ID_GETTER);
            JBlock idGetterBlock = idGetterMethod.body();
            idGetterBlock._return(globalId);

            JMethod idSetterMethod = classOutline.implClass.method(JMod.PUBLIC,
                    VOID_TYPE, ID_SETTER);
            JVar localId = idSetterMethod.param(LONG_TYPE, "_" + ID);
            JBlock idSetterBlock = idSetterMethod.body();
            idSetterBlock.assign(globalId, localId);
        }
        return true;
    }

}

Exemple complet ici .

Un autre exemple ici:

https://blog.jooq.org/2018/02/19/how-to-implement-your-own-xjc-plugin-to-generate-tostring-equals-and-hashcode-methods/ =

Ce sont des plugins disponibles pour générer hashCode, equals, setters-for-list à github aussi.

Références:

Réponse à une question que j'avais posée.

2
Siddharth Trikha

Au cas où quelqu'un chercherait un moyen de se débarrasser de ces initialiseurs paresseux ennuyeux dans le code généré par XJC ... Dans mon Maven POM, cela passe sous <build><plugins>:

<plugin>
    <groupId>org.Apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.8</version>
    <executions>
        <execution>
            <id>remove-jaxb-generated-lazy-initializers</id>
            <phase>process-sources</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <target if="${remove-jaxb-generated-lazy-initializers}">
                    <echo message="Running 'replaceregexp' target on generated sources..."/>
                    <echo message="This removes JAXB-generated lazy initializers from collection accessors."/>
                    <replaceregexp match="if \([\w_]+ == null\) \{\s+[\w_]+ = new ArrayList&lt;[\w_]+&gt;\(\);\s+\}\s+" replace="" flags="g">
                        <fileset dir="${project.build.directory}/generated-sources" includes="**/*.Java"/>
                    </replaceregexp>
                </target>
            </configuration>
        </execution>
    </executions>
</plugin>

Pour les setters, j'ajoute également @lombok.Setter à certaines classes en utilisant org.jvnet.jaxb2_commons:jaxb2-basics-annotate et un fichier de liaisons. Ainsi, les classes finissent par être des beans standard.

J'aimerais l'entendre si quelqu'un connaît une méthode moins hacky - par exemple, un plugin XJC.

1
Lucas Ross