Определите Spring пространства имен JAXB, не используя NamespacePrefixMapper

[В большой степени отредактированный как понимающий прогресс]

Действительно ли возможно заставить 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";

}

пакет-info.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>

После того как эта часть работает, затем проблемные шаги к конфигурированию Jaxb2Marshaller в Spring (Spring 2.5.6, с spring-oxm-tiger-1.5.6, обеспечивающим Jaxb2Marshaller) так, чтобы это обеспечило то же посредством простой конфигурации контекста и вызова для маршалинга ().

Спасибо за Ваш длительный интерес к этой проблеме!

25
задан Gary Rowe 20 July 2010 в 20:11
поделиться

2 ответа

[Некоторые правки, предлагающие альтернативу 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? Вот она, в комплекте с моим собственным NamespacePrefixMapper:

Spring:

<!-- 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: class sun.reflect.GeneratedConstructorAccessor23 cannot access its superclass sun.reflect.ConstructorAccessorImpl

Так что удачи вам в решении этой проблемы. Разгадка: rt.jar в bootclasspath вашего веб-сервера.

[Дополнительные правки для демонстрации подхода JAXB-RI (Reference Implementation)]

Если вы в состоянии внедрить библиотеки JAXB-RI в ваш код, вы можете сделать следующие изменения для получения того же эффекта:

Main:

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

Добавьте следующий JAR из JAXB-RI загрузки (после прохождения через лицензионные путы) из папки /lib:

jaxb-impl.jar

Запуск Main. main() приводит к желаемому результату.

12
ответ дан 28 November 2019 в 21:52
поделиться

(Сильно отредактированный ответ)

Я полагаю, что проблема в вашем коде связана с некоторыми несоответствиями 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"; 

} 

package-info.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>
9
ответ дан 28 November 2019 в 21:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: