2017-02-17 2 views
0

В настоящее время я сталкиваюсь с странным поведением пространства имен JAXB при первом размонтировании и затем сортировке объекта, когда этот объект имеет свойство @XmlAnyElement.Избегайте повторных определений пространства имен в любом элементе

Здесь установка:

package-info.java определение

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

Тип:

@XmlRootElement 
@XmlType(namespace="http://www.example.org") 
public class Message { 

    private String id; 

    @XmlAnyElement(lax = true) 
    private List<Object> any; 

    public String getId() { 
     return id; 
    } 

    public void setId(String id) { 
     this.id = id; 
    } 

    public List<Object> getAny() { 
     if (any == null) { 
      any = new ArrayList<>(); 
     } 
     return this.any; 
    } 
} 

и сам тестовый код:

@Test 
public void simpleTest() throws JAXBException { 

    JAXBContext jaxbContext = JAXBContext.newInstance(Message.class); 
    Marshaller marshaller = jaxbContext.createMarshaller(); 
    marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); 
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 
    marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); 
    Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); 

    String xml = 
      "<example:message xmlns:example=\"http://www.example.org\" xmlns:test=\"http://www.test.org\" xmlns:unused=\"http://www.unused.org\">\n" + 
      " <example:id>id-1</example:id>\n" + 
      " <test:value>my-value</test:value>\n" + 
      " <test:value>my-value2</test:value>\n" + 
      "</example:message>"; 
    System.out.println("Source:\n"+xml); 

    // parsed 
    Object unmarshalled = unmarshaller.unmarshal(new StringReader(xml)); 

    // directly convert it back 
    StringWriter writer = new StringWriter(); 
    marshaller.marshal(unmarshalled, writer); 
    System.out.println("\n\nMarshalled again:\n"+writer.toString()); 
} 

Проблема с эта настройка является та t все «неизвестные» пространства имен неоднократно добавляются к любым элементам.

<example:message xmlns:example="http://www.example.org" xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org"> 
    <example:id>id-1</example:id> 
    <test:value>my-value</test:value> 
    <test:value>my-value2</test:value> 
</example:message> 

становится этим:

<example:message xmlns:example="http://www.example.org"> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value</test:value> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value2</test:value> 
    <example:id>id-1</example:id> 
</example:message> 

Таким образом, как я могу избежать этого! Почему isnt't пространство имен определено один раз в корневом элементе, как на входном XML? Поскольку пространство имен anyElement неизвестно заранее, невозможно зарегистрировать его с помощью определения пакета ...

Кроме того, было бы также возможно, что неиспользуемые пространства имен будут удалены (по запросу)?

ответ

1

Когда JAXB начинает сортировать объекты в XML, он будет иметь некоторый контекст в зависимости от того, где в иерархии объектов и выводится XML. Это потоковая операция по определению, поэтому она будет только смотреть на то, что происходит в данный момент, и на ее текущий контекст.

Так скажите, что он начинает маршал вашего экземпляра Message. Он проверит, какое должно быть имя локального элемента (message), пространство имён должно быть в (http://www.example.org), и если к этому пространству имен привязан определенный префикс (в вашем случае да, префикс example). Пока вы находитесь в своем экземпляре Message, это теперь часть контекста. Если он встречает другие объекты в иерархии, которые находятся в одном и том же пространстве имен, он уже будет иметь его в своем контексте и повторно использует один и тот же префикс, потому что он знает, какой элемент родительского элемента или предка он объявил. Он также проверяет, есть ли какие-либо атрибуты для сортировки, поэтому он может завершить вводный тег. Вывод XML до сих пор выглядит следующим образом:

<example:message xmlns:example="http://www.example.org"> 

Теперь он начинает копаться в поля, которые должны быть ранжированы, но которые не являются атрибутами. Он находит ваше поле List<Object> any и работает. Первой записью является некоторый объект, который будет привязан к элементу value в пространстве имен http://www.test.org. Это пространство имен не привязано ни к одному префиксу в текущем контексте, поэтому оно добавляется, и предпочтительный префикс находится через аннотации пакета-информации (или какой-либо другой поддерживаемый метод). Там нет ничего, кроме того вложенными в значении, которое должно быть ранжированы, так что он может закончить эту часть и выход теперь выглядит следующим образом:

<example:message xmlns:example="http://www.example.org"> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value</test:value> 

Здесь сортировочных первого списка запись заканчивается, значение элемента становится его закрывающий тег и его контекст истекает. К записи следующего списка.Это снова экземпляр объекта, который сортируется до value, снова в том же пространстве имен, но он больше не имеет этого в своем текущем контексте. То же самое происходит.

<example:message xmlns:example="http://www.example.org"> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value</test:value> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value2</test:value> 

Теперь он получает вокруг к String id поле, которое попадает в то же пространство имен Message. Это все еще известно в текущем контексте, потому что мы все еще в сообщении. Чтобы пространство имен еще не было объявлено.

<example:message xmlns:example="http://www.example.org"> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value</test:value> 
    <test:value xmlns:test="http://www.test.org" xmlns:unused="http://www.unused.org">my-value2</test:value> 
    <example:id>id-1</example:id> 
</example:message> 

Так почему не JAXB просто сохранить список имен и их префиксов привязок и поставить те, в корневой элемент? Потому что это потоковый выход. Он не может просто отскочить назад. Это могло бы, если бы оно создавало DOM в памяти, но это было бы не очень эффективно.

И наоборот, почему он не просто пересекает его дерево объектов и создает список привязок пространства имен для использования? Опять же, потому что это не очень эффективно. Кроме того, это может быть просто не полностью известно, как изменится контекст во время обработки. Возможно, мы закончим в каком-то пакете с другим пространством имен, но тем же самым префиксом, что и другое пространство имен. Если в XML мы в настоящее время ничего не связываем с этим префиксом, все в порядке. Как здесь (обратите внимание на второе тестовое пространство имен):

<example:message xmlns:example="http://www.example.org"> 
    <test:value xmlns:test="http://www.test.org">my-value</test:value> 
    <test:value xmlns:test="http://completelydifferenttest">my-value2</test:value> 
    <example:id>id-1</example:id> 
</example:message> 

Но в других ситуациях он должен выбрать несколько другой префикс. Как это семантически эквивалентный документ:

<example:message xmlns:example="http://www.example.org" xmlns:test="http://www.test.org"> 
    <test:value>my-value</test:value> 
    <ns1:value xmlns:ns1="http://completelydifferenttest">my-value2</ns1:value> 
    <example:id>id-1</example:id> 
</example:message> 

Так JAXB просто смотрит на вещи в настоящее время охватывающего контекста, и на местном уровне. Это не идет наперекосяк.

Это еще не решило проблемы. Итак, вот что вы можете сделать.

  • Игнорировать. Вывод, подробный и уродливый, хотя может быть, правильный.
  • Примените преобразование XSLT после сортировки, чтобы очистить пространства имен.
  • Используйте настраиваемое пространство имен для имен.
  • Маршал для XMLEventWriter и предоставит ему персонализированные события для стандартного автора.

Пользовательский картограф - это решение, основанное на эталонной реализации JAXB и использующее внутренние классы. Поэтому его надежная совместимость не может быть гарантирована. Блейз Доуэн объясняет свое использование в этом ответе: https://stackoverflow.com/a/28540700/630136

Последний вариант немного задействован. Вы можете написать сценарий событий, который выводит все пространства имен с привязками префикса по умолчанию на корневом элементе и игнорирует их на последующих элементах, когда это известное пространство имен. Вы бы с самого начала сохраняли некоторый глобальный контекст.

XSLT может быть самым простым, хотя может потребоваться некоторое экспериментирование, чтобы увидеть, как обрабатывает XSLT-процессор.Это один фактически сделал трюк для меня:

<?xml version="1.0" encoding="UTF-8" ?> 
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" 
    xmlns:example="http://www.example.org" xmlns:test="http://www.test.org" 
    xmlns:unused="http://www.unused.org"> 
    <xsl:output method="xml" indent="yes" /> 

    <xsl:template match="node()|@*"> 
     <xsl:copy> 
      <xsl:apply-templates select="node()|@*" /> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:template match="/example:message"> 
     <example:message> 
      <xsl:apply-templates select="node()|@*" /> 
     </example:message> 
    </xsl:template> 

</xsl:transform> 

Обратите внимание, что, если я включаю второй шаблон в матче на /* и использовать <xsl:copy> подход там, это как-то не работает.

Чтобы маршалировать объект и преобразовать полученный XML в один гладкий шаг, обратите внимание на использование the JAXBSource class. Он позволяет использовать объект JAXB в качестве источника преобразования XML.

EDIT: относительно «неиспользуемого» пространства имен. Я помню, что получал кучу пространств имен, которые в какой-то момент не нужны даже для некоторых выпусков JAXB, и в этом случае он оказался связан с аннотациями @XmlSeeAlso, которые были помещены в некоторые классы компилятором XML-Java (отправной точкой была XML-схема). Аннотации удостоверяются, что если класс загружен в JAXBContext, включены классы, указанные в @XmlSeeAlso. Это может сделать создание контекстов намного проще. Но побочным эффектом было то, что он включал в себя множество вещей, которые мне не всегда нужны, и не обязательно всегда нуждались в этом контексте. Я думаю, что JAXB создаст сопоставления имен и префиксов для всего, что он может найти в этот момент.

Говоря об этом, на самом деле это может предложить другое решение вашей проблемы. Если вы помещаете аннотацию @XmlSeeAlso в свой корневой класс и ссылаетесь на другие классы (или, по крайней мере, корень подъярусов), которые потенциально могут быть использованы, возможно, JAXB уже свяжет все пространства имен для найденных пакетов прямо у корня. Я не всегда поклонник аннотации, потому что я не думаю, что суперклассы должны ссылаться на реализации, а классы, более высокие в иерархии, не должны беспокоиться о деталях тех, кто ниже. Но если это не противоречит вашей архитектуре, это стоит того.

+0

Ничего себе. Отличный ответ. Благодаря! Подход XSLT выглядит заманчивым, но я думаю, что вместе с неизвестными пространствами имен это может стать очень уродливым ... Пользовательский сопоставитель будет приятным, но, к сожалению, я не использую эталонную реализацию (просто боль JDK no deps). Поэтому я думаю, что я должен жить с уродством. * Один последний вопрос: почему «неиспользуемый» NS добавлен в шаге 2? Это не нужно ... Разве маршаллер просто добавляет все _remaining_ пространства имен, так как он не знает, что произойдет? – Ingo

+0

@Ingo Если вы используете стандартный дистрибутив Java, вы, вероятно, уже используете эталонную реализацию. Конечно, чтобы действительно иметь возможность импортировать классы во внутренние пространства имен, вам понадобится некоторая зависимость, но она может считаться «предоставленной». Что касается «неиспользуемого» пространства имен, я знал, что что-то забыл! Отправляйся. –

+0

Снова ... спасибо за быстрый ответ и обновление. Как вы можете видеть, у меня нет никаких '@ XmlSeeAlso' ref, поэтому я не знаю, почему это происходит. Любой элемент действительно является расширением, поэтому другие могут добавлять контент самостоятельно. Никакой схемы, никакого поколения, ничего. Просто точка расширения. Хорошо ... тогда _they_ действительно должны отправлять только объявления пространства имен, которые они действительно используют в xml. Никакой очистки с нашей стороны. ;-) – Ingo

Смежные вопросы