Unmarshalling коллекции в JaxB

Предположим, что у меня есть этот класс:

public class A {

    private HashMap<String, B> map;

    @XmlElement
    private void setB(ArrayList<B> col) {
        ...
    }

    private ArrayList<B> getB() {
        ...
    }

}

При попытке развязать XML-документ этому классу с помощью JaxB я замечаю, что вместо вызова метода setB() и отправки мне списка экземпляров B JaxB фактически вызывает getB() и добавляет экземпляры B в возвращенный список, Почему?

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

Спасибо.

Ответы

Ответ 1

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

есть плагин (он никогда не использовал его сам), но может быть полезным: https://jaxb2-commons.dev.java.net/collection-setter-injector/

Ответ 2

Hy,

вы можете использовать его с jaxb, он работает!!! (с Maven....)

<plugin>
            <groupId>org.jvnet.jaxb2.maven2</groupId>
            <artifactId>maven-jaxb2-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <args>
                    <arg>-Xcollection-setter-injector</arg>
                </args>
                <plugins>
                    <plugin>
                        <groupId>net.java.dev.vcc.thirdparty</groupId>
                        <artifactId>collection-setter-injector</artifactId>
                        <version>0.5.0-1</version>
                    </plugin>
                </plugins>
                <schemaDirectory>src/schemas</schemaDirectory>
                <generateDirectory>src/main/java</generateDirectory>
                <extension>true</extension>
            </configuration>
        </plugin>

и вы получите свой сеттер для своей коллекции

Надеюсь, что это поможет людям

до свидания

Ответ 3

Примечание. Я EclipseLink JAXB (MOXy) и член экспертной группы JAXB 2 (JSR-222).

Поведение, которое вы видите, будет варьироваться в зависимости от реализаций JAXB. Если вы не инициализируете значение для свойства List, тогда EclipseLink JAXB (MOXy) вызовет метод set, как вы ожидаете.

Дополнительная информация


Пример

А

package forum1032152;

import java.util.ArrayList;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class A {

    private ArrayList<B> b;

    @XmlElement
    public void setB(ArrayList<B> col) {
        System.out.println("Called setB");
        for(B b : col) {
            System.out.println(b);
        }
        this.b = col;
    }

    public ArrayList<B> getB() {
        return b;
    }

}

В

package forum1032152;

public class B {

}

Demo

package forum1032152;

import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(A.class);

        File xml = new File("src/forum1032152/input.xml");
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.unmarshal(xml);
    }

}

Input.xml

<?xml version="1.0" encoding="UTF-8"?>
<a>
    <b></b>
    <b></b>
</a>

Выход

Called setB
[email protected]
[email protected]

Ответ 4

JAXB имеет проблемы с поддержкой интерфейсов и абстрактных классов; он обычно не знает, какой подкласс должен создать экземпляр. Проблема заключается в том, что общий шаблон имеет класс по строкам:

ArrayList list;

@XMLElement
public List getList() {return this.list;}

Чтобы обойти это, JAXB даже не пытается создать экземпляр класса свойств (например, List), полученного из пары getter/setter, если это коллекция. Он просто предполагает, что он не нулевым и модифицируемым.

Вероятно, самая простая работа - отметить ваш бизнес-интерфейс с помощью @XMLTransient и добавить другую пару getter/setter с @XMLElement для представления данных, которые вы хотите открыть JAXB. Я обычно делаю их защищенными, а не публичными, потому что я не хочу, чтобы поведение JAXB несколько пугало как часть публичного контракта моих классов.

Ответ 5

Jaxb2 UnMarshaller определяет интерфейс прослушивателя, который вызывается в любое время, когда объект был неархалирован. Вы можете определить пользовательский прослушиватель для вызова методов setter во всех коллекциях (или на под-объектах). Это должно быть довольно легко сделать с любым из классов bean utils. Я ищу существующую реализацию, хотя я ее не вижу.

JAXBContext context = JAXBContext.newInstance( classesToBeBound );
m_unmarshaller = context.createUnmarshaller();
m_unmarshaller.setListener(
  new Unmarshaller.Listener() {
    public void afterUnmarshal(Object target, Object parent) {
     for (Property p : getBeanProperties(target.getClass()))
      if (p.isCollectionType() || p.isCompositeType())
        p.invokeSetter(p.invokeGetter());
    }
  });

Если вы используете фреймворк spring, его довольно просто:

    new Unmarshaller.Listener() {
         public void afterUnmarshal(Object target, Object parent) {
             BeanWrapper wrapper = new BeanWrapperImpl(target);
             for (PropertyDescriptor pd : wrapper.getPropertyDescriptors()) {
                 if (pd.getPropertyType() != null) {
                         if (!BeanUtils.isSimpleProperty(pd.getPropertyType())) {
                             try {
                                 Method setter = pd.getWriteMethod();
                                 if (setter != null) {
                                     Method getter = pd.getReadMethod();
                                     if (getter != null)
                                         setter.invoke(target, getter.invoke(target));
                                 }
                             }
                             catch (Exception ex) {
                                 s_logger.error("can't invoke setter", ex);
                             }
                         }
                 }
             }
         }
    }

Ответ 6

Вы можете просто использовать массив вместо списка)

Ответ 7

The reason I want the setter to be called is that the list is actually
just a temporary storage from which I want to build the map field,
so I thought to do it in the setter.

JAXB может обрабатывать карты непосредственно, следовательно, это может сделать вызов SETB() спорный пункт. Если это приемлемое решение для вас, см. Пример , который я поддерживаю в своем блоге, чтобы создать адаптер для карт в JAXB.