Определить пространства имен 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;
}
ChildElementWiспасибоLink.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="#" onclick="location.href='http://www.example.org'; return false;";
}
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"?>
<ns2:Root_Element xmlns:ns1="http://www.w3.org/1999/xlink" xmlns:ns2="www.example.org/abc">
<ns2:childElementWithXlink ns1:href="#" onclick="location.href='http://www.example.org'; return false;"/>
</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="#" onclick="location.href='http://www.example.org'; return false;"/>
</abc:Root_Element>
После того, как эта часть работает, проблема переходит к настройке Jaxb2Marshaller в Spring (Spring 2.5.6, с spring -oxm-tiger-1.5.6, предоставляющим Jaxb2Marshaller), чтобы он предоставлял то же самое с помощью простой конфигурации контекста и вызова маршала().
Спасибо за ваш постоянный интерес к этой проблеме!
Ответы
Ответ 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? Ну вот и все, с моим собственным 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 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
Так удачи, сортируя это. Подсказка: 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() приводит к желаемому результату.
Ответ 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;
}
ChildElementWiспасибоLink.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>
Ответ 3
@Blaise: Можете ли вы обновить документацию MOXy с помощью этой информации:
Определить пространства имен Spring JAXB без использования NamespacePrefixMapper
Я думаю, что здесь не описано, как вы можете настроить префиксы пространства имен.
Спасибо!
Ответ 4
JAXB-реализация в JDK 7 поддерживает префикс пространства имен. Я пробовал с JDK 1.6.0_21 без везения.