【问题标题】:XmlAdapter not working as expected in JAXB RIXmlAdapter 在 JAXB RI 中未按预期工作
【发布时间】:2011-11-09 02:19:09
【问题描述】:

我正在尝试实现一个 XmlAdapter 来修改某些对象属性的编组/解组。特别是,我尝试使用此处描述的NullStringAdapter

Jaxb: xs:attribute null values

NullStringAdapter 的目标是将空值编组为空字符串,反之亦然。

与上面描述的示例和我的代码的唯一区别是,我想将适配器应用于元素,而不是属性,所以我拥有的是:

@XmlElement
@XmlJavaTypeAdapter(NullStringAdapter.class)
public String getSomeValue() {
    return someValue;  //someValue could be null, in that case the adapter should marshall it as an empty string
}

但是,经过一些调试,我意识到在从 Java 到 XML 的编组过程中从未调用过 Adapter 方法!。当 XmlElement 值为 null 时会发生这种情况。 当此值不为 null 时,适配器方法将按预期调用。

感谢您的帮助!

【问题讨论】:

  • 以下示例可能会有所帮助:blog.bdoughan.com/search/label/XmlAdapter
  • 非常感谢 Blaise 的链接!!根据这些示例,在我看来我已经正确地实现了适配器。你知道是否有办法强制使用它,即使适配的对象有一个空值??
  • 也许从未设置元素值?请提供NullStringAdapter 的代码,也许这会给出提示...
  • @Sergio - 当值为 null 时,JAXB RI 似乎不会调用 XmlAdapter,但如果您有兴趣,EclipseLink JAXB (MOXy) 会:blog.bdoughan.com/2011/05/…
  • @BlaiseDoughan 这确实更有意义。想知道 JAXB 规范是否对此有什么要说的,或者它是否取决于实现的摆布。

标签: java xml jaxb


【解决方案1】:

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

但是,经过一些调试,我意识到适配器方法是 在从 Java 到 XML 的编组过程中从未调用过!出现这种情况 当 XmlElement 值为空时。当此值不同于 null,适配器方法按预期调用。

这种行为因 JAXB 的实现而异。当字段/属性为空时,JAXB 参考实现不会调用 XmlAdapter 上的封送方法,但 MOXy 会。

JAXB 规范所说的(第 5.5.1 节简单属性)

get 或 is 方法返回在 上一小节。如果返回 null,则认为该属性 在它所代表的 XML 内容中不存在。

该语句的 MOXy 解释是,字段/属性的值实际上是经过XmlAdapter 后的值。这是支持behaviour that Sergio is looking for所必需的。

【讨论】:

  • 这几乎是最有意义的。在我看来,null 应该是一个与任何其他值一样的值,并且可以由适配器解释。
【解决方案2】:

当然,如果输入中没有任何元素触发该操作,则永远不会调用适配器。在您链接的那个示例中发生的情况是出现了一个具有空值的属性:

<element att="" />

这里的关键是一个att 属性,但它有一个空字符串。因此,JAXB 解组器会将其呈现给二传手。但是,由于上面声明了一个适配器,它会通过那里并变成一个空值。

如果你有这个

<element />

这是另一个故事。没有att 属性,因此永远不需要调用setter。

出现但没有内容的元素与完全没有元素之间是有区别的。前者基本上可以认为是包含一个空的String,而后者只是“不存在”。

编辑:用这些类测试...

Bean.java

package jaxbadapter;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name="Test")
public class Bean {

    @XmlElement
    @XmlJavaTypeAdapter(NullStringAdapter.class)
    private String someValue;

    public Bean() {
    }

    public String getSomeValue() {
        return someValue;
    }

    public void setSomeValue(final String someValue) {
        this.someValue = someValue;
    }

}

NullStringAdapter.java

package jaxbadapter;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class NullStringAdapter extends XmlAdapter<String, String> {

    @Override
    public String unmarshal(final String v) throws Exception {
        if("".equals(v)) {
            return null;
        }
        return v;
    }

    @Override
    public String marshal(final String v) throws Exception {
        if(null == v) {
            return "";
        }
        return v;
    }

}

ObjectFactory.java

package jaxbadapter;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;


@XmlRegistry
public class ObjectFactory {

    public ObjectFactory() {
    }

    public Bean createBean() {
        return new Bean();
    }

    @XmlElementDecl(name = "Test")
    public JAXBElement<Bean> createTest(Bean value) {
        return new JAXBElement<>(new QName("Test"), Bean.class, null, value);
    }

}

Main.java

package jaxbadapter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.transform.stream.StreamResult;

public class Main {

    public static void main(String[] args) throws Exception {

        final JAXBContext context = JAXBContext.newInstance("jaxbadapter");

        final Marshaller m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

        final ObjectFactory of = new ObjectFactory();

        final Bean b1 = new Bean();

        final Bean b2 = new Bean();
        b2.setSomeValue(null);

        final Bean b3 = new Bean();
        b3.setSomeValue("");

        m.marshal(of.createTest(b1), System.out);
        System.out.println("");

        m.marshal(of.createTest(b2), System.out);
        System.out.println("");

        m.marshal(of.createTest(b3), System.out);
        System.out.println("");


    }

}

这是输出:

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

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

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Test>
    <someValue></someValue>
</Test>

其实让我有点吃惊。然后我尝试将 getter 更改为 return someValue == null ? "" : someValue; 无济于事。然后在 getter 上设置一个断点,发现它永远不会被调用。

显然,JAXB 使用反射来尝试检索值,而不是在使用 XmlAccessType.FIELD 时通过 setter。铁杆。现在,您可以改用XmlAccessType.PROPERTY 并注释getter 或setter 来绕过它...

package jaxbadapter;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(name="Test")
public class Bean {

    private String someValue;

    public Bean() {
    }

    @XmlElement
    @XmlJavaTypeAdapter(NullStringAdapter.class)
    public String getSomeValue() {
        return someValue;
    }

    public void setSomeValue(final String someValue) {
        this.someValue = someValue;
    }

}

...但这仍然没有帮助。适配器的marshal 方法仅在最后一个设置了空字符串的测试用例中调用了一次。显然它首先调用了 getter,当它返回 null 时,它完全跳过了适配器的东西。

我能想出的唯一解决方案就是在此完全放弃使用适配器,并将替换项放在 getter 中,确保使用 XmlAccessType.PROPERTY

package jaxbadapter;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(name="Test")
public class Bean {

    private String someValue;

    public Bean() {
    }

    @XmlElement
//  @XmlJavaTypeAdapter(NullStringAdapter.class)
    public String getSomeValue() {
        return someValue == null ? "" : someValue;
    }

    public void setSomeValue(final String someValue) {
        this.someValue = someValue;
    }

}

这对我有用。但是,如果您自己创建带有 JAXB 注释的类而不是通过 XJC 从模式中生成它们,这才是真正的选择。

也许有人可以澄清一下为什么适配器会因空值而被跳过,以及是否有办法改变这种行为。

【讨论】:

  • 我认为适配器将在 marshaller/unmarshaller 的 Courtains 后面调用。如果不是这种情况,请您向我解释我应该怎么做?谢谢
  • @Sergio 好吧,如果它返回 null 值,它应该适用于 getter。也就是说,它应该适用于编组。但它不适用于解组,因为没有任何东西“触发”对适配器的调用。在从 Java 编组到 XML 期间它不起作用吗?
  • @Sergio 你是对的......请参阅编辑以获取我的测试和可能的解决方案。不过,这取决于您的用例。
  • 非常感谢您的大回答!。在这种情况下,我也真的在寻找可以澄清适配器行为的人!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-31
  • 2021-06-04
  • 2022-01-24
  • 2015-05-11
  • 2020-05-15
  • 2014-10-31
  • 2018-02-12
相关资源
最近更新 更多