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

Posted

技术标签:

【中文标题】在不使用 NamespacePrefixMapper 的情况下定义 Spring JAXB 命名空间【英文标题】:Define Spring JAXB namespaces without using NamespacePrefixMapper 【发布时间】:2011-03-18 09:46:32 【问题描述】:

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

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

这个想法是让一个类与另一个类具有“具有”关系,而另一个类又包含一个具有不同命名空间的属性。为了更好地说明这一点,请考虑以下使用 JDK1.6.0_12 的项目大纲(我可以在工作中得到的最新版本)。我在包 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:

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

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

【问题讨论】:

你能用香草JAXBContext 来解决这个问题,即不涉及 Spring 吗?看起来Jaxb2Marshaller 应该与问题无关。另外,您使用的是哪个 JDK 和/或 JAXB 版本? (附:嗨,加里 :) 嘿,skaffman :-) 根据需要进行了编辑。这是我的疯子。 【参考方案1】:

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

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

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

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

如果你发现自己处于这个位置,这里有一个 Maven sn-p 可以让你摆脱困境:

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

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: 类 sun.reflect.GeneratedConstructorAccessor23 无法访问其超类 sun.reflect.ConstructorAccessorImpl

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

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

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

主要:

// 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() 会产生所需的输出。

【讨论】:

另一种方法是使用 JAXB-RI 而不是 JDK 1.6 中内置的。这样,您将获得一个可预测的类路径,并且 NamespacePrefixMapper 在类名中没有 internal :) JAXB-RI 已移至 org.glassfish.jaxb 并根据 this answer 退出 sun-package【参考方案2】:

(经过大量编辑的回复)

我认为您的代码中的问题是由于某些命名空间 URI 不匹配造成的。有时您使用的是 "http://www.example.org/abc",有时您使用的是 "www.example.org/abc"。以下应该可以解决问题:

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); 
        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); 
       

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

【讨论】:

感谢 Blaise 对此感兴趣。为了更好地回答您的问题,我对原始帖子进行了一些编辑。您会注意到我添加了一个具有不同名称空间(XLink 名称)的子元素,我在原始帖子中没有明确说明(抱歉)。 嗨,Blaise,我尝试使用更新的 RootElement 类复制您的结果,但不幸的是,如果所有其他代码都与我在帖子中所写的一样,那么它会因附加 (ns3) 而失败命名空间。您介意查看我发布的内容以确保它仍然符合您的设置吗?感谢您的帮助:) 我错过了额外的 ns3 命名空间声明。如果您从 ChildElementWithXLink 中删除 @XmlRootElement 它就会消失(请参阅上面的编辑答案)。你的模型中需要那个注解吗? 嗨,布莱斯。我再次尝试达到与您相同的点,但我仍然失败了。我只是无法摆脱“ns1”命名空间而不是“xlink”。我已经在 Windows XP 上的 JDK 1.6.0_12 和 Mac 上的 JDK 1.6.0_20 上进行了尝试,并且都给出了相同的结果。我肯定使用了问题中的代码,加上您的编辑,但我的结果不同。我想您使用的是不同的 JDK 或操作系统。你介意澄清一下你的环境吗?我必须承认,我开始对这件事完全失去希望了。 嗨,加里,我已经重做我的答案。看起来最终您有时会使用命名空间“example.org/abc”,有时会使用“www.example.org/abc”,这会导致额外的命名空间声明。我的环境是 Windows XP 上的 JDK 1.6.0_20。【参考方案3】:

@Blaise:你能用这个信息更新 MOXy 的文档吗:

Define Spring JAXB namespaces without using NamespacePrefixMapper

我认为那里没有描述如何配置命名空间前缀。 谢谢!

【讨论】:

【参考方案4】:

JDK 7 中的 JAXB 实现支持命名空间前缀。我尝试过使用 JDK 1.6.0_21,但没有成功。

【讨论】:

以上是关于在不使用 NamespacePrefixMapper 的情况下定义 Spring JAXB 命名空间的主要内容,如果未能解决你的问题,请参考以下文章

我可以在不使用 Apollo 的情况下使用 graphql 进行反应吗?

如何在不安装 Zend 框架的情况下使用 Zend 库

在不使用存储过程的情况下循环 n 次

为啥 glDrawElments() 在不使用任何着色器的情况下工作?

在不使用地图Android的情况下获取用户(纬度,经度)位置[重复]

在不使用 ForEach 循环的情况下遍历列表