在JaxB中解组集合

14

假设我有这个类:

public class A {

    private HashMap<String, B> map;

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

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

}

当我尝试使用JaxB将xml文档解组为这个类时,我注意到JaxB实际上调用了getB()方法并将B实例添加到返回的列表中,而不是调用setB()方法并向我发送B实例列表。为什么?

我想要调用setter的原因是该列表实际上只是一个临时存储,我希望在其中构建map字段,因此我想在setter中完成这个操作。

谢谢。

7个回答

8

+1 指出了 jaxb 处理集合的方式,但那个插件并没有帮助。那是 XJC 代码生成器的插件,不会改变 JAXB 运行时行为。 - skaffman
我阅读了一些旧的电子邮件线程,声称这种行为正在JaxB 2.1中得到修复。我期望在JaxbContext.newInstance(classes, properties)中有一个属性来控制编组行为,但我找不到它。 - Justin

6

你好,

你可以使用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>

你可以获得集合的setter方法

希望这能帮到大家

再见


选择包时,您还可以添加参数<generatePackage>com.your.package</generatePackage>。 - George

5

注意: 我是EclipseLink JAXB (MOXy)的负责人,也是JAXB 2 (JSR-222)专家组成员。

你看到的行为会因JAXB实现而异。如果你没有为List属性初始化值,则EclipseLink JAXB (MOXy)将按照你的期望调用set方法。

更多信息请参见


例子

A

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

}

B

package forum1032152;

public class B {

}

演示

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
forum1032152.B@8bdcd2
forum1032152.B@4e79f1

3

JAXB在支持接口和抽象类方面存在问题;通常它不知道要实例化哪个子类。问题在于,按照以下模式定义一个类是很常见的:

ArrayList list;

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

为了避免这种情况,JAXB不会尝试实例化从getter/setter对派生的属性类(例如List)如果它是一个Collection。它只是假设它是不为null并且可以修改的。
可能最简单的解决方法是使用@XMLTransient标记您的业务接口,并添加另一个带有@XMLElement的getter/setter对,用于公开要向JAXB公开的数据视图。我通常将这些设置为protected而不是public,因为我不想让有些笨拙的JAXB行为成为我的类的公共契约的一部分。

1
Jaxb2 UnMarshaller 定义了一个监听器接口,每当对象被取消编组时就会调用该接口。您可以定义一个自定义监听器来调用所有集合(或子对象)上的 setter 方法。使用任何一个 bean 工具类都应该很容易实现。虽然我没有找到现有的实现,但我正在寻找一个。
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);
                             }
                         }
                 }
             }
         }
    }

1
你可以使用数组而不是List。

0
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中映射的适配器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接