Почему JAXB не генерирует сеттеры для списков

Когда я генерирую классы JAXB из XSD, элементы с maxOccurs="unbounded" получают сгенерированный для них метод получения, но не метод установки, как показано ниже:

/**
 * 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;
}

Комментарий к методу ясно дает понять, как я могу его использовать, но мой вопрос заключается в следующем:
Почему JAXB просто не генерирует сеттер, следуя правилам Java Beans? Я знаю, что я я могу сам написать метод установки, но есть ли преимущество в подходе, предложенном в сгенерированном методе получения?

Это мой 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>

person Ahmad Y. Saleh    schedule 17.12.2012    source источник


Ответы (6)


Вот обоснование из спецификации JAXB - стр. 60.

Примечание разработчика. Для свойства List не существует метода установки. Геттер возвращает список по ссылке. Элемент можно добавить в список, возвращаемый методом получения, с помощью соответствующего метода, определенного в java.util.List. Обоснование такого дизайна в JAXB 1.0 заключалось в том, чтобы позволить реализации обернуть список и иметь возможность выполнять проверки при добавлении или удалении содержимого из списка.

Таким образом, если реализация списка переопределяла добавление/удаление для выполнения проверки, замена этого «специального» списка (например) на ArrayList приведет к отмене этих проверок.

person jdessey    schedule 26.08.2014
comment
К сожалению, без установщика другие компоненты, такие как BlazeDS, не распознают getItems() как часть свойства и вернутся к имени поля (которое защищено) и в конечном итоге передадут нуль независимо от того, что было в списке. Добавление установщика (который вызывает getItems, очищает его и добавляет новые значения) решает эту проблему. - person Patrick; 17.08.2015
comment
Честно говоря, я думаю, что это серьезное нарушение того, как должны работать Java-бины. Я был бы в порядке с JAXB, пытающимся вызвать .add(), когда он видит коллекцию, возвращаемую свойством компонента, у которого нет установщика. Но, если я определил сеттер, это действительно должно предполагать, что я сделал это по какой-то причине, и я, черт возьми, ожидаю, что он не будет обойден, и иногда я даже возвращаю немодифицируемые оболочки коллекции, чтобы заставить людей не испортить мои внутренности. Извините за разглагольствования, но эта ошибка уже потратила впустую два дня моего времени. - person zakmck; 04.12.2015
comment
Помимо многих инструментов, которые не могут найти сеттеры (поскольку они предполагают правила Java Beans), основным недостатком, который я нахожу в своем проекте, является то, что во многих местах вызываются List геттеры, что автоматически приводит к их инициализации в список Empty, а затем во время сортировки я должен настроить не отправлять пустой список. Это не позволяет мне различать случай, когда я намеренно хочу установить для свойства пустой список. У меня есть вариант использования, когда установлен список Empty, удалите, очистите список - person Siddharth Trikha; 11.07.2018

Ссылка для: Нет установщика для списка

Код в методе получения гарантирует, что список будет создан. Соответствующего установщика нет, что означает, что все добавления или удаления элементов списка должны выполняться в "живом" списке.

Как говорится в цитате, нет установщика, поскольку при использовании метода получения он гарантирует, что новый экземпляр списка будет инициализирован, если его нет.

И после этого, когда вам нужно что-то добавить или удалить, вам придется использовать

getElement3().add(Type);

ОБНОВЛЕНИЕ: разница в маршаллинге для null и пустого списка

Пример, где список null

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

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

}

ВЫВОД будет

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

Пример, когда список пустой

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

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

}

ВЫВОД будет:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<list-demo>
    <list/>
</list-demo>
person Narendra Pathai    schedule 17.12.2012
comment
Я до сих пор не понимаю, зачем JAXB гарантировать, что новый экземпляр списка будет инициализирован, если его нет!!! какое ему дело, JAXB работает точно так же, если значение списка было null или пустой список. и предотвращение NullPointerExceptions здесь выходит за рамки JAXB IMO. - person Ahmad Y. Saleh; 17.12.2012
comment
Для JAXB нулевой и пустой список различны. Следующим образом: если список null, то вы не найдете этот элемент в сгенерированном xml, но если список имеет размер 0, то в xml будет присутствовать элемент xml <list/>. Так что они оба разные. - person Narendra Pathai; 17.12.2012
comment
если свойство nillable истинно в xsd, то оно не должно генерировать эту проверку null. НАСКОЛЬКО МНЕ ИЗВЕСТНО. - person Narendra Pathai; 17.12.2012
comment
Я обновил ответ, чтобы показать вам разницу между нулевым и пустым списком. - person Narendra Pathai; 17.12.2012
comment
Я вижу, @XmlElementWrapper сделал свое дело. Однако JAXB не генерирует его по умолчанию, и на самом деле он также использует тип доступа к полю по умолчанию, поэтому инициализация списка в геттере не повлияет на сортировку... моя точка зрения в любом случае заключается в нарушении правил Java Bean, не создавая сеттер - person Ahmad Y. Saleh; 17.12.2012
comment
Но что мне делать, если мне нужно проверить список, и если размер равен 0, установить null вместо списка? У меня есть несколько проверок xml в сервисе мыла, которые я не могу изменить, и если один список представлен, другой должен быть нулевым. - person Danny Dan; 15.05.2015

Согласитесь с беспокойством Патрика выше. Если бы я кодировал сгенерированные классы Java напрямую, я был бы рад выполнить обязательство, но я использую интроспективный инструмент, ожидающий либо установщика, либо члена с прямым доступом. Успешно использовал плагин для XJC из https://github.com/highsource/jaxb2-basics/wiki/JAXB2-Setters-Plugin и добавление аргумента -B-Xsetter в wsimport

person Chris Klopfenstein    schedule 14.10.2016

Можно написать свои собственные XJC plugin для своих конкретных требований.

В этом примере мы пытаемся добавить поле id типа long в каждый сгенерированный файл Java из 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;
    }

}

Полный пример здесь.

Другой пример здесь:

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

Их плагины доступны для создания hashCode, equals, setters-for-list на github.

Ссылки:

Ответ на вопрос, который я задал.

person Siddharth Trikha    schedule 19.07.2018

На случай, если кто-то здесь ищет способ избавиться от этих раздражающих ленивых инициализаторов в коде, сгенерированном XJC... В моем Maven POM это идет под <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>

Для сеттеров я также добавляю @lombok.Setter к определенным классам, используя org.jvnet.jaxb2_commons:jaxb2-basics-annotate и файл привязок. Таким образом, классы становятся стандартными bean-компонентами.

Я хотел бы услышать это, если кто-нибудь знает менее хакерский способ, например, плагин XJC.

person Lucas Ross    schedule 25.11.2017

Нужно добавить зависимость внутри плагина. Например, как показано ниже,

<plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>jaxb2-maven-plugin</artifactId>
                    <version>1.5</version>
                    <dependencies>
                    <dependency>
                        <groupId>org.andromda.thirdparty.jaxb2_commons</groupId>
                        <artifactId>collection-setter-injector</artifactId>
                        <version>1.0</version>
                    </dependency>
                    </dependencies>
                    <configuration>
                        <schemaDirectory>${project.basedir}/epc</schemaDirectory>
                        <arguments>-Xcollection-setter-injector</arguments>
                        <clearOutputDir>false</clearOutputDir>
                        <extension>true</extension>
                    </configuration>
                    <executions>
                        <execution>
                            <id>generate-java-from-xsd-1</id>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>xjc</goal>
                            </goals>
                            <configuration>
                                <packageName>${package name}</packageName>
                                <schemaFiles>example.xsd</schemaFiles>
                                <schemaDirectory>${project.basedir}/epc</schemaDirectory>
                                <bindingFiles>example_1.xjb</bindingFiles>
                                <bindingDirectory>${project.basedir}/generate</bindingDirectory>
                                <staleFile>${project.build.directory}/jaxb2/.xjc1StaleFlag</staleFile>
                            </configuration>
                        </execution>
              </plugin>

person Pawan Gunjkar    schedule 30.06.2020