5

私の質問は、文字列が空だがnullでない場合にJAXBで空のタグをマーシャリングするのを防ぐ方法に非常に似ています

違いは、ビルドごとにすべての JAXB タイプがスキーマから生成されるため、package-info.java にアノテーションを追加できないことです。また、可能であれば JAXB プロバイダーを変更したくありません。

私が達成したいのは、空の文字列を設定しても要素が作成されないということですが、多くのスキーマから生成されたすべての JAXB タイプに対してこれを設定する必要があります。生成されたすべての JAXB クラスのすべての文字列フィールドにこれを適用する方法はありますか?

更新 次の変更を加えることで、スキーマ内のすべての文字列に対して XML アダプターを生成することができました。

プロジェクト POM で、これを maven-jaxb2-plugin に追加しました。

<bindingDirectory>src/main/resources</bindingDirectory>
<bindingIncludes>
    <include>bindings.xjb</include>
</bindingIncludes>

そして、ここに私の bindings.xjb ファイルがあります:

<jxb:bindings xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb" version="2.1">
    <jxb:globalBindings>
                    <jxb:javaType name="java.lang.String" xmlType="xs:token"
                        parseMethod="com.project.Formatter.parseString"
                        printMethod="com.project.Formatter.printString"/>
    </jxb:globalBindings>
</jxb:bindings>

そしてフォーマット方法:

public static String printString(final String value)
{
    if (StringUtils.isBlank(value))
    {
        return null;
    }

    return value;
}

問題は、これにより JAXB の奥深くで Null Pointer Exception が発生することです。スタックトレースは次のとおりです。

Caused by: java.lang.NullPointerException
    at com.sun.xml.bind.v2.runtime.output.SAXOutput.text(SAXOutput.java:158)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:321)
    at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:210)
    at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:209)
    at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:250)
    at com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:98)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
    at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:65)
    at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:168)
    at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:152)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
    at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:322)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:681)
    at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.serializeBody(SingleElementNodeProperty.java:150)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:156)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:185)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:305)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:312)
    at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:71)
    at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:490)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:328)
    at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:257)
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:103)

この問題の原因は、次の方法に要約されます。

com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor.CompositeTransducedAccessorImpl.hasValue(BeanT)

上記のメソッドは、アダプターが実行される前に値が null でない場合に要素をレンダリングします。

要素をレンダリングするかどうかを決定する前にアダプターを実行するように、JAXB で使用されるアクセサーをオーバーライドする方法はありますか? 私が望むものを達成する別の方法はありますか?

4

3 に答える 3

14

注: 私はEclipseLink JAXB (MOXy)のリーダーであり、JAXB (JSR-222)エキスパート グループのメンバーです。

あなたが行ったことは正しいです。表示されているエラーは、JAXB リファレンス実装のバグであると私が信じていることが原因です。JAXB RI は、 から返される null 値を処理できる必要がありますXmlAdapter。このユース ケースは EclipseLink JAXB (MOXy) で動作します。以下に例を示します。

文字列アダプター

以下は、XML スキーマから Java モデルを生成した後に得られるものとほぼ同じことを行う実装です ( http://blog.bdoughan.com/2011/08/xml-schema-to-java-generating を参照)。 html )。

package forum11894193;

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

public class StringAdapter extends XmlAdapter<String, String> {

    @Override
    public String marshal(String string) throws Exception {
        if("".equals(string)) {
            return null;
        }
        return string;
    }

    @Override
    public String unmarshal(String string) throws Exception {
        return string;
    }

}

パッケージ情報

グローバル アダプターを登録しているため、package-info以下のようなクラスから参照されます (参照: http://blog.bdoughan.com/2012/02/jaxb-and-package-level-xmladapters.html )。

@XmlJavaTypeAdapters({
    @XmlJavaTypeAdapter(value=StringAdapter.class, type=String.class)
})
package forum11894193;

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

String以下は、いくつかのフィールドを持つドメイン クラスのサンプルです。XmlAdapterはパッケージ レベルで登録されているため、そのパッケージ内のマップされたすべての文字列フィールド/プロパティに適用されます。

package forum11894193;

import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    String a;
    String b;
    String c;

}

デモ

以下のデモ コードでは、Rootset のインスタンスをいくつかのフィールドに作成し、""それを XML にマーシャリングします。

package forum11894193;

import javax.xml.bind.*;

public class Demo {

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

        Root root = new Root();
        root.a = "";
        root.b = "b";
        root.c = "";

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }

}

JAXB RI を使用した出力

この例で JAXB RI を使用すると、NPE が発生します。スタック トレースは異なりますが、異なるマーシャル メソッドを使用している可能性が最も高いです。また、JDK に含まれているバージョンの JAXB RI を使用していcom.sun.xml.internal.bind.v2ます。

Exception in thread "main" java.lang.NullPointerException
    at com.sun.xml.internal.bind.v2.runtime.output.Encoded.setEscape(Encoded.java:96)
    at com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput.doText(UTF8XmlOutput.java:294)
    at com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput.text(UTF8XmlOutput.java:283)
    at com.sun.xml.internal.bind.v2.runtime.output.IndentingUTF8XmlOutput.text(IndentingUTF8XmlOutput.java:141)
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:293)
    at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:179)
    at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$1.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:166)
    at com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:239)
    at com.sun.xml.internal.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:87)
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:306)
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:561)
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:290)
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:462)
    at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:314)
    at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:243)
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75)
    at forum11894193.Demo.main(Demo.java:17)

EclipseLink JAXB (MOXy)を使用した出力

MOXy を JAXB プロバイダーとして使用すると、目的の出力が得られます。MOXy を JAXB プロバイダーとして指定する方法については、http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html を参照して ください

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <b>b</b>
</root>
于 2012-08-13T09:46:28.370 に答える
0

空の文字列も値です。それが要素が作成される理由です。そうは言っても、文字列が空の場合、セッターメソッドで変数をnullに設定するのはどうですか?

また、このスレッド JAXB をチェックしてください : how to make JAXB NOT to unmarshal empty string to 0

于 2012-08-10T03:02:19.017 に答える
0

以下のコードをマーシャルクラスに使用します

public static String toXml(Object o, Class clazz, boolean isFormatted, boolean isEmptyNodes) {
        try {
            Map<String, Object> properties = new HashMap<String, Object>(1);
            if(isEmptyNodes) {
                SessionEventListener sessionEventListener = new NullPolicySessionEventListener();
                properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener);
            } else {
                SessionEventListener sessionEventListener = new DiscardEmptyTagSessionEventListener();
                properties.put(JAXBContextProperties.SESSION_EVENT_LISTENER, sessionEventListener);
            }

            // Create a JaxBContext
            JAXBContext jc = JAXBContext.newInstance(new Class[] {clazz}, properties);
            StringWriter sw = new StringWriter();

            // Create the UnMarshaller Object using the JaxB Context
            Marshaller marshaller = jc.createMarshaller();

            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, isFormatted);
            // remove the xml version line from the output
            marshaller.setProperty("com.sun.xml.bind.xmlDeclaration", Boolean.FALSE);
            // Marshal the employee object to XML and print the output to console
            marshaller.marshal(o, sw);

            return sw.toString();
        } catch (JAXBException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
}
于 2017-07-20T17:41:57.990 に答える