定义 Spring JAXB 命名空间而不使用 NamespacePrefixMapper

2022-09-01 17:40:26

[随着理解的进展,进行了大量编辑]

是否有可能让Spring Jaxb2Marshaller使用一组自定义的命名空间前缀(或者至少尊重模式文件/注释中给出的前缀),而不必使用NamenamePrefixMapper的扩展?

这个想法是让一个类与另一个类具有“具有”关系,而另一个类又包含具有不同命名空间的属性。为了更好地说明这一点,请考虑以下使用JDK1.6.0_12的项目大纲(我在工作中可以得到的最新数据)。我在包 org.example.domain 中有以下内容:

主要.java:

package org.example.domain;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

public class Main {
  public static void main(String[] args) throws JAXBException {
    JAXBContext jc = JAXBContext.newInstance(RootElement.class);

    RootElement re = new RootElement();
    re.childElementWithXlink = new ChildElementWithXlink();

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

}

根元素.java:

package org.example.domain;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(namespace = "www.example.org/abc", name="Root_Element")
public class RootElement {
  @XmlElement(namespace = "www.example.org/abc")
  public ChildElementWithXlink childElementWithXlink;

}

ChildElementWithXLink.java:

package org.example.domain;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchemaType;

@XmlRootElement(namespace="www.example.org/abc", name="Child_Element_With_XLink")
public class ChildElementWithXlink {
  @XmlAttribute(namespace = "http://www.w3.org/1999/xlink")
  @XmlSchemaType(namespace = "http://www.w3.org/1999/xlink", name = "anyURI")
  private String href="http://www.example.org";

}

软件包信息.java:

@javax.xml.bind.annotation.XmlSchema(
    namespace = "http://www.example.org/abc",
    xmlns = {
          @javax.xml.bind.annotation.XmlNs(prefix = "abc", namespaceURI ="http://www.example.org/abc"),
          @javax.xml.bind.annotation.XmlNs(prefix = "xlink", namespaceURI = "http://www.w3.org/1999/xlink")
            }, 
    elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
    package org.example.domain;

运行 Main.main() 会给出以下输出:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:Root_Element xmlns:ns1="http://www.w3.org/1999/xlink" xmlns:ns2="www.example.org/abc">
<ns2:childElementWithXlink ns1:href="http://www.example.org"/>
</ns2:Root_Element>

而我想要的是:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<abc:Root_Element xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:abc="www.example.org/abc">
<abc:childElementWithXlink xlink:href="http://www.example.org"/>
</abc:Root_Element>

一旦这部分工作正常,问题就转移到在Spring中配置Jaxb2Marshaller(Spring 2.5.6,spring-oxm-tiger-1.5.6提供Jaxb2Marshaller),以便它通过简单的上下文配置和对marshal()的调用来提供相同的功能。

感谢您对这个问题的持续关注!


答案 1

[提供JAXB-RI替代方案的一些编辑在本文末尾]

在经历了很多挠头之后,我终于不得不接受,对于我的环境(JDK1.6.0_12 Windows XP上的JDK1.6.0_20,在Mac Leopard上),如果不诉诸NamespacePrefixMapper的邪恶,我就无法完成这项工作。为什么它是邪恶的?因为它强制依赖生产代码中的内部 JVM 类。这些类不构成 JVM 和代码之间可靠接口的一部分(即它们在 JVM 的更新之间更改)。

在我看来,Sun应该解决这个问题,或者有更深入知识的人可以添加这个答案 - 请做!

继续前进。因为 NamespacePrefixMapper 不应该在 JVM 之外使用,所以它不包含在 javac 的标准编译路径中(rt.jar的子部分,由 ct.sym 控制)。这意味着任何依赖于它的代码都可能在IDE中编译良好,但在命令行(即Maven或Ant)上会失败。为了克服这个问题,rt.jar文件必须显式包含在构建中,即使这样,如果路径中有空格,Windows似乎也会遇到麻烦。

如果您发现自己处于此位置,这里有一个Maven片段,可以帮助您摆脱困境:

<dependency>
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-impl</artifactId>
  <version>2.1.9</version>
  <scope>system</scope>
  <!-- Windows will not find rt.jar if it is in a path with spaces -->
  <systemPath>C:/temp/rt.jar</systemPath>
</dependency>

请注意垃圾硬编码路径,用于rt.jar的奇怪地方。你可以通过{java.home}/lib/rt.jar的组合来解决这个问题,这将适用于大多数操作系统,但由于Windows空间问题不能保证。是的,您可以使用配置文件并相应地激活...

或者,在 Ant 中,您可以执行以下操作:

<path id="jre.classpath">
  <pathelement location="${java.home}\lib" />
</path>
// Add paths for build.classpath and define {src},{target} as usual
<target name="compile" depends="copy-resources">
  <mkdir dir="${target}/classes"/>
  <javac bootclasspathref="jre.classpath" includejavaruntime="yes" debug="on" srcdir="${src}" destdir="${target}/classes" includes="**/*">
    <classpath refid="build.classpath"/>
  </javac>
</target>    

那么Jaxb2Marshaller Spring的配置呢?好吧,它在这里,完成了我自己的NamenamePrefixMapper:

春天:

<!-- JAXB2 marshalling (domain objects annotated with JAXB2 meta data) -->
<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPaths">
  <list>
    <value>org.example.domain</value>
  </list>
</property>
<property name="marshallerProperties">
  <map>
    <!-- Good for JDK1.6.0_6+, lose 'internal' for earlier releases - see why it's evil? -->
    <entry key="com.sun.xml.internal.bind.namespacePrefixMapper" value-ref="myCapabilitiesNamespacePrefixMapper"/>
    <entry key="jaxb.formatted.output"><value type="boolean">true</value></entry>
  </map>
</property>
</bean>

<!-- Namespace mapping prefix (ns1->abc, ns2->xlink etc) -->
<bean id="myNamespacePrefixMapper" class="org.example.MyNamespacePrefixMapper"/>

然后我的命名空间前缀映射器代码:

public class MyNamespacePrefixMapper extends NamespacePrefixMapper {

  public String getPreferredPrefix(String namespaceUri,
                               String suggestion,
                               boolean requirePrefix) {
    if (requirePrefix) {
      if ("http://www.example.org/abc".equals(namespaceUri)) {
        return "abc";
      }
      if ("http://www.w3.org/1999/xlink".equals(namespaceUri)) {
        return "xlink";
      }
      return suggestion;
    } else {
      return "";
    }
  }
}

好吧,就是这样。我希望这有助于某人避免我所经历的痛苦。哦,顺便说一句,如果您在Jetty中使用上述邪恶方法,您可能会遇到以下异常:

java.lang.IllegalAccessError: class sun.reflect.GeneratedConstructorAccessor23 无法访问其超类 sun.reflect.ConstructorAccessorImpl

所以祝你好运,把那个整理出来。线索:rt.jar在 Web 服务器的引导类路径中。

[额外的编辑以显示 JAXB-RI(参考实现)方法]

如果您能够在代码中引入 JAXB-RI 库,则可以进行以下修改以获得相同的效果:

主要:

// Add a new property that implies external access
marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper());

MyNamespacePrefixMapper:

// Change the import to this
import com.sun.xml.bind.marshaller.NamespacePrefixMapper;

从 /lib 文件夹中从 JAXB-RI 下载(跳过许可证圈后)添加以下 JAR:

jaxb-impl.jar

运行 Main.main() 会产生所需的输出。


答案 2

(大量编辑回复)

我相信代码中的问题是由于某些命名空间 URI 不匹配造成的。有时你使用http://www.example.org/abc,有时使用“www.example.org/abc”。以下操作应该可以解决问题:

主要.java

package org.example.domain;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

public class Main {

    public static void main(String[] args) throws JAXBException { 
        JAXBContext jc = JAXBContext.newInstance(RootElement.class); 
        System.out.println(jc);

        RootElement re = new RootElement(); 
        re.childElementWithXlink = new ChildElementWithXlink(); 

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

根元素.java

package org.example.domain; 

import javax.xml.bind.annotation.XmlElement; 
import javax.xml.bind.annotation.XmlRootElement; 

@XmlRootElement(namespace="http://www.example.org/abc", name="Root_Element") 
public class RootElement { 
  @XmlElement(namespace = "http://www.example.org/abc") 
  public ChildElementWithXlink childElementWithXlink; 

}

ChildElementWithXLink.java

package org.example.domain;

import javax.xml.bind.annotation.XmlAttribute; 
import javax.xml.bind.annotation.XmlRootElement; 
import javax.xml.bind.annotation.XmlSchemaType; 

@XmlRootElement(namespace="http://www.example.org/abc", name="Child_Element_With_XLink") 
public class ChildElementWithXlink { 
  @XmlAttribute(namespace = "http://www.w3.org/1999/xlink") 
  @XmlSchemaType(namespace = "http://www.w3.org/1999/xlink", name = "anyURI") 
  private String href="http://www.example.org"; 

} 

包信息.java

@javax.xml.bind.annotation.XmlSchema( 
    namespace = "http://www.example.org/abc", 
    xmlns = { 
          @javax.xml.bind.annotation.XmlNs(prefix = "abc", namespaceURI ="http://www.example.org/abc"), 
          @javax.xml.bind.annotation.XmlNs(prefix = "xlink", namespaceURI = "http://www.w3.org/1999/xlink") 
            },  
    elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) 
    package org.example.domain; 

现在运行 Main.main() 会给出以下输出:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<abc:Root_Element xmlns:abc="http://www.example.org/abc" xmlns:xlink="http://www.w3.org/1999/xlink">
    <abc:childElementWithXlink xlink:href="http://www.example.org"/>
</abc:Root_Element>

推荐