在不使用 NamespacePrefixMapper 的情况下定义 Spring JAXB 命名空间

2022-01-19 00:00:00 xml spring java jaxb

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

[Heavily edited as understanding progresses]

是否可以让 Spring Jaxb2Marshaller 使用一组自定义的命名空间前缀(或至少尊重架构文件/注释中给出的前缀)而不必使用 NamespacePrefixMapper 的扩展?

Is it possible to get Spring Jaxb2Marshaller to use a custom set of namespace prefixes (or at least respect the ones given in the schema file/annotations) without having to use an extension of a NamespacePrefixMapper?

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

The idea is to have a class with a "has a" relationship to another class that in turn contains a property with a different namespace. To better illustrate this consider the following project outline which uses JDK1.6.0_12 (the latest I can get my hands on at work). I have the following in the package org.example.domain:

Main.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);
  }

}

RootElement.java:

RootElement.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:

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() 给出以下输出:

Running Main.main() gives the following output:

<?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(Spring 2.5.6,spring-oxm-tiger-1.5.6 提供 Jaxb2Marshaller)中配置 Jaxb2Marshaller,以便它通过简单的方式提供相同的上下文配置和对 marshal() 的调用.

Once this part is working, then the problem moves on to configuring the Jaxb2Marshaller in Spring (Spring 2.5.6, with spring-oxm-tiger-1.5.6 providing Jaxb2Marshaller) so that it provides the same by means of a simple context configuration and a call to marshal().

感谢您一直关注这个问题!

Thank you for your continued interest in this problem!

推荐答案

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

[Some edits to offer a JAXB-RI alternative are at the end of this post]

经过多次挠头后,我终于不得不接受,对于我的环境(Windows XP 上的 JDK1.6.0_12 和 Mac Leopard 上的 JDK1.6.0_20),如果不诉诸邪恶,我就无法完成这项工作是命名空间前缀映射器.为什么是邪恶的?因为它在生产代码中强制依赖内部 JVM 类.这些类不构成 JVM 和您的代码之间的可靠接口的一部分(即它们在 JVM 更新之间发生变化).

Well after much head scratching I've finally had to accept that for my environment (JDK1.6.0_12 on Windows XP and JDK1.6.0_20 on Mac Leopard) I just can't make this work without resorting to the evil that is the NamespacePrefixMapper. Why is it evil? Because it forces a reliance on an internal JVM class in your production code. These classes do not form part of a reliable interface between the JVM and your code (i.e. they change between updates of the JVM).

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

In my opinion Sun should address this issue or someone with deeper knowledge could add to this answer - please do!

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

Moving on. Because NamespacePrefixMapper is not supposed to be used outside of the JVM it is not included in the standard compile path of javac (a subsection of rt.jar controlled by ct.sym). This means that any code that depends on it will probably compile fine in an IDE, but will fail at the command line (i.e. Maven or Ant). To overcome this the rt.jar file must be explicitly included in the build, and even then Windows seems to have trouble if the path has spaces in it.

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

If you find yourself in this position, here is a Maven snippet that will get you out of trouble:

<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 空间问题并不能保证.是的,您可以使用配置文件并相应地激活...

Note the rubbish hard coded path to a weird place for rt.jar. You could get around this with a combination of {java.home}/lib/rt.jar which will work on most OSs but because of the Windows space issue is not guaranteed. Yes, you can use profiles and activate accordingly...

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

Alternatively, in Ant you can do the following:

<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 配置呢?好了,这里有我自己的 NamespacePrefixMapper:

And what of the Jaxb2Marshaller Spring configuration? Well here it is, complete with my own NamespacePrefixMapper:

春天:

<!-- 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"/>

然后是我的 NamespacePrefixMapper 代码:

Then my NamespacePrefixMapper code:

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 中使用上述邪恶的方法,你可能会遇到以下异常:

Well there it is. I hope this helps someone avoid the pain I went through. Oh, by the way, you may run into the following exception if you use the above evil approach within Jetty:

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

java.lang.IllegalAccessError: class sun.reflect.GeneratedConstructorAccessor23 cannot access its superclass sun.reflect.ConstructorAccessorImpl

祝你好运.提示:您的 Web 服务器的引导类路径中的 rt.jar.

So good luck sorting that one out. Clue: rt.jar in the bootclasspath of your web server.

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

[Extra edits to show the JAXB-RI (Reference Implementation) approach]

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

If you're able to introduce the JAXB-RI libraries into your code you can make the following modifications to get the same effect:

主要:

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

MyNamespacePrefixMapper:

MyNamespacePrefixMapper:

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

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

Add the following JAR from JAXB-RI download (after jumping through license hoops) from the /lib folder:

jaxb-impl.jar

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

Running Main.main() results in the desired output.

相关文章