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.