【问题标题】:Controlling namespace prefixes in JAXB在 JAXB 中控制命名空间前缀
【发布时间】:2011-02-02 10:42:39
【问题描述】:

在编组对象时,jaxb 如何确定命名空间前缀声明列表?我使用 xjc 为 ebics (ebics schema) 编译 java 类。当我为 ebicsRequest 创建一个实例时,它看起来像这样:


<?xml version="1.0" encoding="UTF-16"?>
<ns2:ebicsRequest xmlns:ns2="http://www.ebics.org/H003" Revision="1" Version="H003" xmlns="http://www.ebics.org/H003" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ns4="http://www.ebics.org/S001" xmlns:ns5="http://www.ebics.org/H000">
    <ns2:header authenticate="true">
        <ns2:static>
            <ns2:HostID>SIZBN001</ns2:HostID>
            <ns2:Nonce>A5488F43223063171CA0FA59ADC635F0</ns2:Nonce>
            <ns2:Timestamp>2009-08-04T08:41:56.967Z</ns2:Timestamp>
            <ns2:PartnerID>EBICS</ns2:PartnerID>
            <ns2:UserID>EBIX</ns2:UserID>
            <ns2:Product Language="de">EBICS-Kernel V2.0.4, SIZ/PPI</ns2:Product>
            <ns2:OrderDetails>
                <ns2:OrderType>FTB</ns2:OrderType>
                <ns2:OrderID>A037</ns2:OrderID>
                <ns2:OrderAttribute>OZHNN</ns2:OrderAttribute>
                <ns2:StandardOrderParams/>
            </ns2:OrderDetails>
            <ns2:BankPubKeyDigests>
                <ns2:Authentication Algorithm="RSA" Version="X002">...</ns2:Authentication>
                <ns2:Encryption Algorithm="RSA" Version="E002">...</ns2:Encryption>
            </ns2:BankPubKeyDigests>
            <ns2:SecurityMedium>0000</ns2:SecurityMedium>
            <ns2:NumSegments>1</ns2:NumSegments>
        </ns2:static>
        <ns2:mutable>
            <ns2:TransactionPhase>Initialisation</ns2:TransactionPhase>
        </ns2:mutable>
    </ns2:header>
    <ns2:AuthSignature>
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <ds:Reference URI="#xpointer(//*[@authenticate='true'])">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                <ds:DigestValue>CSbjPbiNcFqSl6lCI1weK5x1nMeCH5bTQq5pedq5uI0=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>...</ds:SignatureValue>
    </ns2:AuthSignature>
    <ns2:body>
        <ns2:DataTransfer>
            <ns2:DataEncryptionInfo authenticate="true">
                <ns2:EncryptionPubKeyDigest Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" Version="E002">dFAYe281vj9NB7w+VoWIdfHnjY9hNbZLbHsDOu76QAE=</ns2:EncryptionPubKeyDigest>
                <ns2:TransactionKey>...</ns2:TransactionKey>
            </ns2:DataEncryptionInfo>
            <ns2:SignatureData authenticate="true">...</ns2:SignatureData>
        </ns2:DataTransfer>
    </ns2:body>
</ns2:ebicsRequest>

我使用了自定义NamespacePrefixMapper 来声明ds 和xsi 的默认命名空间和前缀。对于命名空间 ds 它工作正常。但是对于默认命名空间,它没有。它被声明两次,一次是 ns2,一次是“”,后者来自我的自定义 NamespacePrefixMapper.getPreDeclaredNamespaceUris。我在这门课上玩了很多。我也尝试使用package-info.java,但我无法让jaxb 使用"http://www.ebics.org/H003" 作为默认命名空间。我也不明白的是 ns4 和 ns5 的外观,它们根本不是 xml 文档的一部分。

我的 NamespacePrefixMapper 类看起来像


public class NamespacePrefixMapperImpl extends NamespacePrefixMapper implements NamespaceContext {
    private static final String[] EMPTY_STRING = new String[0];

    private Map prefixToUri = null;
    private Map uriToPrefix = null;

    private void init(){
    prefixToUri = new HashMap();

    prefixToUri.put("", "http://www.ebics.org/H003" );
    prefixToUri.put("ds", "http://www.w3.org/2000/09/xmldsig#" );
    prefixToUri.put("xsi", "http://www.w3.org/2001/XMLSchema-instance" );
    prefixToUri.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI  );
    prefixToUri.put(XMLConstants.XMLNS_ATTRIBUTE , XMLConstants.XMLNS_ATTRIBUTE_NS_URI );

    uriToPrefix = new HashMap();
    for(String prefix : prefixToUri.keySet()){
        uriToPrefix.put(prefixToUri.get(prefix), prefix);
    }
    }

    @Override
    public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
        if (uriToPrefix == null)
        init();

        if (uriToPrefix.containsKey(namespaceUri)){
            return uriToPrefix.get(namespaceUri);
        }

        return suggestion;
    }

    @Override
    public String[] getContextualNamespaceDecls() {
    // TODO Auto-generated method stub
    return EMPTY_STRING;
    }

    @Override
    public String[] getPreDeclaredNamespaceUris() {
    // TODO Auto-generated method stub
    return EMPTY_STRING;

    }

    @Override
    public String[] getPreDeclaredNamespaceUris2() {
    return new String [] {"", prefixToUri.get("")};

    }

    public String getNamespaceURI(String prefix) {
    if (prefixToUri == null)
            init();

    if (prefixToUri.containsKey(prefix)) {
        return prefixToUri.get(prefix);
    } else {
        return XMLConstants.NULL_NS_URI;
    }
    }

    public String getPrefix(String namespaceURI) {
    if (uriToPrefix == null)
            init();

        if (uriToPrefix.containsKey(namespaceURI)){
        return uriToPrefix.get(namespaceURI);
    } else {
        return null;
    }
    }

    public Iterator getPrefixes(String namespaceURI) {
    if (uriToPrefix == null)
            init();

    List prefixes = new LinkedList();

    if (uriToPrefix.containsKey(namespaceURI)){
        prefixes.add(uriToPrefix.get(namespaceURI));
    }
    return prefixes.iterator();
    }


}

我正在使用


Manifest-Version: 1.0
Ant-Version: Apache Ant 1.6.5
Created-By: 1.5.0-b64 (Sun Microsystems Inc.)
Specification-Title: Java Architecture for XML Binding
Specification-Version: 2.0
Specification-Vendor: Sun Microsystems, Inc.
Implementation-Title: JAXB Reference Implementation 
Implementation-Version: 2.0.2
Implementation-Vendor: Sun Microsystems, Inc.
Implementation-Vendor-Id: com.sun
Extension-Name: com.sun.xml.bind
Build-Id: b01
Class-Path: jaxb-api.jar activation.jar jsr173_1.0_api.jar jaxb1-impl.
 jar

Name: com.sun.xml.bind.v2.runtime
Implementation-Version: 2.0.2-b01-fcs

【问题讨论】:

  • 能否附上您的 NamespacePrefixMapper 实现?
  • 嗨 skaffman,关于使用 XMLStreamWriter 的问题,如上述问题的答案中所建议的那样:我想编组为 org.w3c.dom.Document。我无法通过编组文档片段并对其进行签名来为文档的 jaxb 实例添加正确的签名。编组后的片段看起来与整个编组文档的片段不同(同样是因为命名空间声明),因此在编组整个文档时签名无效。所以我去编组到一个 Dom 文档,然后添加签名,然后序列化。 :-(

标签: java xml jaxb xml-namespaces


【解决方案1】:

出于性能原因,JAXB 总是将 JAXBContext 已知的所有名称空间添加到 XML 文档的根元素中。有关更多信息,请参阅 Kohsuke 在JAXB-103 上的评论。

我发现解决此问题的唯一方法是在使用 JAXB 创建文档后自己遍历文档,并使用以下帮助程序类删除所有未使用的命名空间:

public class RemoveUnusedNamespaces {

    private static final String XML_NAMESPACE_SCHEMA_INSTANCE = "http://www.w3.org/2001/XMLSchema-instance";

    private static final String XML_NAMESPACE_NAMESPACE = "http://www.w3.org/2000/xmlns/";

    private interface ElementVisitor {

        void visit(Element element);

    }

    public void process(Document document) {
        final Set<String> namespaces = new HashSet<String>();

        Element element = document.getDocumentElement();
        traverse(element, new ElementVisitor() {

            public void visit(Element element) {
                String namespace = element.getNamespaceURI();
                if (namespace == null)
                    namespace = "";
                namespaces.add(namespace);
                NamedNodeMap attributes = element.getAttributes();
                for (int i = 0; i < attributes.getLength(); i++) {
                    Node node = attributes.item(i);
                    if (XML_NAMESPACE_NAMESPACE.equals(node.getNamespaceURI()))
                        continue;
                    String prefix;
                    if (XML_NAMESPACE_SCHEMA_INSTANCE.equals(node.getNamespaceURI())) {
                        if ("type".equals(node.getLocalName())) {
                            String value = node.getNodeValue();
                            if (value.contains(":"))
                                prefix = value.substring(0, value.indexOf(":"));
                            else
                                prefix = null;
                        } else {
                            continue;
                        }
                    } else {
                        prefix = node.getPrefix();
                    }
                    namespace = element.lookupNamespaceURI(prefix);
                    if (namespace == null)
                        namespace = "";
                    namespaces.add(namespace);
                }
            }

        });
        traverse(element, new ElementVisitor() {

            public void visit(Element element) {
                Set<String> removeLocalNames = new HashSet<String>();
                NamedNodeMap attributes = element.getAttributes();
                for (int i = 0; i < attributes.getLength(); i++) {
                    Node node = attributes.item(i);
                    if (!XML_NAMESPACE_NAMESPACE.equals(node.getNamespaceURI()))
                        continue;
                    if (namespaces.contains(node.getNodeValue()))
                        continue;
                    removeLocalNames.add(node.getLocalName());
                }
                for (String localName : removeLocalNames)
                    element.removeAttributeNS(XML_NAMESPACE_NAMESPACE, localName);
            }

        });
    }

    private final void traverse(Element element, ElementVisitor visitor) {
        visitor.visit(element);
        NodeList children = element.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE)
                continue;
            traverse((Element) node, visitor);
        }
    }

}

【讨论】:

  • 如何第二次遍历文档?使用字符串操作?
  • 不,它是在文档被序列化之前完成的,方法是从根节点开始遍历 DOM 树中每个元素的所有子节点。
  • 我在我的服务中添加了一个 SOAPHandler 并在 handleMessage(SOAPMessageContext context) 方法中调用了这个实用程序,它就像一个魅力。现在我的 CXF 网络服务剥离了未使用的命名空间,并且它连接的 .NET 服务不会抱怨,因为 SOAP 消息的根元素大于元素声明的允许大小,即只有 4000 字节!干杯重启。
  • 对此清理操作的影响/性能有任何经验吗?
  • @edbras 我使用 JMH 进行了自己的个人测试。我平均每秒执行 78 次操作,而添加了 RemoveUnusedNamespaces 则每秒执行 74 次操作。我在一个小的 XML 文档中删除了大约 30 个未使用的命名空间
【解决方案2】:

EclipseLink JAXB (MOXy) 使用 @XmlSchema 注释中指定的前缀(我是 MOXy 领导)。查看我对类似问题的回答以获取示例:

【讨论】:

  • 嗨,布莱斯,感谢您的回答。我用我的 jaxb 实现尝试了这个解决方案,见下文。我的代码在 JEE 环境中运行,我想使用可用的 jaxb 库。如果我找不到解决方案,我可能会检查 MOXy。 @javax.xml.bind.annotation.XmlSchema(namespace = "ebics.org/H003", xmlns = { @javax.xml.bind.annotation.XmlNs(prefix = "", namespaceURI = "ebics.org/H003") , @javax.xml .bind.annotation.XmlNs(prefix = "ds", namespaceURI = "w3.org/2000/09/xmldsig#") }, elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
  • @Wilberforce - 不知何故我错过了这条评论。如果您使用的是 JAXB 的参考实现,您还可以使用 NamespacePrefixMappiner:jaxb.java.net/guide/Changing_prefixes.html
  • 是的,我使用了 NamespacePrefixMappiner(参见上面的代码)。我现在发现我的问题与 xjc 生成 java 代码有关,该代码错误地生成了属性命名空间设置为“”的注释。这迫使 jaxb 使用 "" 作为默认命名空间来覆盖任何自定义设置。我没有弄清楚为什么会发生这种情况,但是在手动更改代码后一切正常。
  • @Wilberforce:您应该这样做作为对您问题的回答,以便我们可以投票。 (鼓励自我回答,只要它是一个答案。)
【解决方案3】:

我发现让 JAXB 删除 ns2 前缀的方法是在 xs:schema 元素中包含以下属性:elementFormDefault="qualified"。所以它看起来像这样:

<xs:schema targetNamespace="urn:blah:blah" xmlns="urn:blah:blah" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">

【讨论】:

    【解决方案4】:

    我使用 xjc 生成了我的 JAXB 类,但是我使用的 SOAP WebService 强制我遵循一些规则,例如不使用命名空间前缀。

    这是无效的:

    <envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe" xmlns:ns2="http://www.w3.org/2000/09/xmldsig#">
         <idLote>123</idLote>
         <evento>
             <ns2:Signature/>
         </evento>
    </envEvento>
    

    这是有效的:

    <envEvento versao="1.00" xmlns="http://www.portalfiscal.inf.br/nfe">
        <idLote>123</idLote>
        <evento>
            <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"/>
        </evento> 
    </envEvento>
    

    正如所指出的,JAXB 将命名空间声明放在根元素中。

    为了克服这个问题,我使用的第一种方法是避免上下文中不必要的元素。

    例如,像这样设置编组器的上下文:

    JAXBContext.newInstance("path.to.package");
    

    可以导致 JAXB 对命名空间进行一些不必要的声明。

    有时,我可以摆脱烦人的 xmlns="http://www.w3.org/2000/09/xmldsig#" 只需使用必要的根元素设置上下文:

    JAXBContext.newInstance(MyRootElement.class);
    

    当第一种方法不够用时,我使用的第二种方法是让整个上下文使用相同的命名空间。只需将每个命名空间声明(如@XmlElement 或@XSchema)中不需要的“http://www.w3.org/2000/09/xmldsig#”更改为允许的唯一命名空间(http://www.portalfiscal.inf.br/nfe

    然后,我只是在所需的孩子处创建一个属性:

    @XmlAttribute(name="xmlns")
    String xmlns = "http://www.w3.org/2000/09/xmldsig#";
    

    现在我在正确的元素中从根中声明命名空间,而不使用任何前缀。

    【讨论】:

    • 这是一个巨大的雷神般的锤子,但是,嘿......我正在为 ns2 前缀而苦苦挣扎,因为它假定默认的“”为 ns1,因此为“”。这解决了我的问题。直到我找到更好的解决方案
    【解决方案5】:

    爬了很多帖子,使用NamespacePrefixMapper的解决方案对JDK版本有依赖性(将来可能会破坏代码)或者XML DOM树操作看起来很复杂。

    我的蛮力方法是操纵生成的 XML 本身。

    /**
     * Utility method to hide unused xmlns definition in XML root.
     * @param sXML Original XML string.
     * @return
     */
    public static String hideUnUsedNamespace(String sXML) {
        int iLoc0 = sXML.indexOf("?><");
        int iLoc1 = sXML.indexOf("><",iLoc0+3)+1;
        String sBegin = sXML.substring(0,iLoc0+2);
        String sHeader = sXML.substring(iLoc0+2, iLoc1-1);
        String sRest = sXML.substring(iLoc1);
        //System.out.println("sBegin=" + sBegin);
        //System.out.println("sHeader=" + sHeader);
        //System.out.println("sRest=" + sRest);
    
        String[] saNS = sHeader.split(" ");
        //System.out.println("saNS=" + java.util.Arrays.toString(saNS));
    
        StringBuffer sbHeader = new StringBuffer();
        for (String s: saNS) {
            //System.out.println(s);
            if (s.startsWith("xmlns:")) {
                String token = "<" + s.substring(6,s.indexOf("="));
                //System.out.println("token=" + token + ",indexOf(token)=" + sRest.indexOf(token));
                if (sRest.indexOf(token) >= 0) {
                    sbHeader = sbHeader.append(s).append(" ");
                    //System.out.println("...included");
                }
            } else {
                sbHeader = sbHeader.append(s).append(" ");
            }
        }
        return (sBegin + sbHeader.toString().trim() + ">" + sRest);
    }
    
    /**
     * Main method for testing
     */
    public static void main(String[] args) {
        String sXML ="<?xml version=\"1.0\" encoding=\"UTF-16\"?><ns2:ebicsRequest xmlns:ns2=\"http://www.ebics.org/H003\" Revision=\"1\" Version=\"H003\" xmlns=\"http://www.ebics.org/H003\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:ns4=\"http://www.ebics.org/S001\" xmlns:ns5=\"http://www.ebics.org/H000\"><ns2:header authenticate=\"true\"><ns2:static><ns2:HostID>SIZBN001</ns2:HostID><ns2:Nonce>A5488F43223063171CA0FA59ADC635F0</ns2:Nonce><ns2:Timestamp>2009-08-04T08:41:56.967Z</ns2:Timestamp><ns2:PartnerID>EBICS</ns2:PartnerID><ns2:UserID>EBIX</ns2:UserID><ns2:Product Language=\"de\">EBICS-Kernel V2.0.4, SIZ/PPI</ns2:Product><ns2:OrderDetails><ns2:OrderType>FTB</ns2:OrderType><ns2:OrderID>A037</ns2:OrderID><ns2:OrderAttribute>OZHNN</ns2:OrderAttribute><ns2:StandardOrderParams/></ns2:OrderDetails><ns2:BankPubKeyDigests><ns2:Authentication Algorithm=\"RSA\" Version=\"X002\">...</ns2:Authentication><ns2:Encryption Algorithm=\"RSA\" Version=\"E002\">...</ns2:Encryption></ns2:BankPubKeyDigests><ns2:SecurityMedium>0000</ns2:SecurityMedium><ns2:NumSegments>1</ns2:NumSegments></ns2:static><ns2:mutable><ns2:TransactionPhase>Initialisation</ns2:TransactionPhase></ns2:mutable></ns2:header><ns2:AuthSignature><ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/><ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/><ds:Reference URI=\"#xpointer(//*[@authenticate='true'])\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/><ds:DigestValue>CSbjPbiNcFqSl6lCI1weK5x1nMeCH5bTQq5pedq5uI0=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>...</ds:SignatureValue></ns2:AuthSignature><ns2:body><ns2:DataTransfer><ns2:DataEncryptionInfo authenticate=\"true\"><ns2:EncryptionPubKeyDigest Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\" Version=\"E002\">dFAYe281vj9NB7w+VoWIdfHnjY9hNbZLbHsDOu76QAE=</ns2:EncryptionPubKeyDigest><ns2:TransactionKey>...</ns2:TransactionKey></ns2:DataEncryptionInfo><ns2:SignatureData authenticate=\"true\">...</ns2:SignatureData></ns2:DataTransfer></ns2:body></ns2:ebicsRequest>";
    
        System.out.println("Before=" + sXML);
        System.out.println("After =" + hideUnUsedNamespace(sXML));
    }
    

    输出显示未使用的 xmlns 命名空间被过滤掉:

    <ns2:ebicsRequest xmlns:ns2="http://www.ebics.org/H003" Revision="1" Version="H003" xmlns="http://www.ebics.org/H003" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-11
      • 2017-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-29
      • 1970-01-01
      相关资源
      最近更新 更多