Защо се изисква проверка за основен елемент в Jaxb2Marshaller?

Използвам Jaxb2Marshaller за маршал Java beans чрез пролетна анотация @ResponseBody. За JSON маршалингът работи добре. Но за xml непрекъснато получавах HTTP 406 отговор. Малко копаене в клас Jaxb2Marshaller разкрива, че той проверява за @XmlRootElement за ограничени класове (вижте фрагмента по-долу).

Докато генерирах Java код от xsd, моят pojo не съдържа @XmlRootElement и правилният конвертор на съобщения не беше идентифициран от AnnotationMethodHandlerAdapter и накрая доведе до 406.

Вместо автоматично генериране на Java код от xsd, създадох свой собствен pojo клас и използвах @XmlRootElement. Тогава маршалингът работи добре.

Искам да разбера защо е важно @XmlRootElement да проверява за ограничени класове. Или по някакъв начин да посочите елемент като @XmlRootElement в xsd.

Кодов фрагмент от Jaxb2Marshaller:

public boolean supports(Class clazz) {
    return supportsInternal(clazz, true);
}

private boolean supportsInternal(Class<?> clazz, boolean checkForXmlRootElement) {
    if (checkForXmlRootElement && clazz.getAnnotation(XmlRootElement.class) == null) {
        return false;
    }
    if (clazz.getAnnotation(XmlType.class) == null) {
        return false;
    }
    if (StringUtils.hasLength(getContextPath())) {
        String className = ClassUtils.getQualifiedName(clazz);
        int lastDotIndex = className.lastIndexOf('.');
        if (lastDotIndex == -1) {
            return false;
        }
        String packageName = className.substring(0, lastDotIndex);
        String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":");
        for (String contextPath : contextPaths) {
            if (contextPath.equals(packageName)) {
                return true;
            }
        }
        return false;
    }
    else if (!ObjectUtils.isEmpty(classesToBeBound)) {
        return Arrays.asList(classesToBeBound).contains(clazz);
    }
    return false;
}

Редактиране: Отговорът на Blaise ми помогна да реша проблема с @XmlRootElement. Но все пак, ако някой има информация защо е необходима проверка за XmlRootElement, ще бъде добра информация.


person Jaydeep Patel    schedule 26.01.2011    source източник
comment
Актуализирах отговора си, надявам се, че това помага да се изяснят нещата.   -  person bdoughan    schedule 27.01.2011


Отговори (3)


Защо анотацията @XmlRootElement се проверява за

Spring изисква основен елемент при маршалинг на обекта към XML. JAXB предоставя два механизма за това:

  1. Анотацията @XmlRootElement
  2. Обвиване на основния обект в екземпляр на JAXBElement.

Тъй като обектът не е обвит в JAXBElement Spring, се гарантира, че другото условие е изпълнено.

Как да генерирате @XmlRootElement

JAXB ще генерира анотация @XmlRootElement за всички глобални елементи в XML схема. Следното ще предизвика @XmlElement:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <xsd:element name="foo">
       <xsd:complexType>
           ...
       </xsd:complextType>
    </xsd:element>
</xsd:schema>

Когато @XmlRootElement не е генериран

Анотация @XmlRootElement няма да бъде генерирана за глобални типове.

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <xsd:element name="foo" type="foo"/>
   <xsd:complexType name="foo">
       ...
   </xsd:complexType>
</xsd:schema>

Вместо това глобалните елементи, свързани с глобалните типове, се улавят в класа ObjectFactory (анотиран с @XmlRegistry) под формата на анотации @XmlElementDecl. Тези анотации

package generated;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;


@XmlRegistry
public class ObjectFactory {

    private final static QName _Foo_QNAME = new QName("", "foo");

    public Foo createFoo() {
        return new Foo();
    }

    @XmlElementDecl(namespace = "", name = "foo")
    public JAXBElement<Foo> createFoo(Foo value) {
        return new JAXBElement<Foo>(_Foo_QNAME, Foo.class, null, value);
    }

}

Анотацията @XmlElementDecl предоставя подобна информация като @XmlRootElement и може да се използва за демаршал операции. Имплементациите на JAX-RS вероятно не използват @XmlElementDecl, тъй като маршалните операции ще изискват обектът да бъде обвит в JAXBElement обект, за да предостави името/пространството от имена на коренния елемент.

person bdoughan    schedule 26.01.2011
comment
Благодаря, сега мога да генерирам POJO с елемент @XmlRootElement, променящ xsd, както посочихте. - person Jaydeep Patel; 27.01.2011

това е известен проблем: https://jira.springsource.org/browse/SPR-7931

„Проверката за анотация на @XmlRootElement трябва да бъде незадължителна в Jaxb2Marshaller“

person Matthias M.    schedule 05.10.2012
comment
Моля, включете нещо повече от връзка в отговора си. Ако връзката някога се повреди, публикацията няма да помогне на бъдещите потребители. - person Mick MacCallum; 06.10.2012

Можете да използвате JaxbElement за класове, които нямат @XmlRootElement анотация. Анотацията @XmlRootElement се поставя само към нереферирани обекти от най-високо ниво, ако генерирате своя код от xsd

Edit

See @Blaise Doughan answer.

@XmlRootElement will be placed only if there is no reference to that type in another type.
person fmucar    schedule 27.01.2011
comment
@XmlRootElement се добавя за всички генерирани класове, съответстващи на глобални елементи, независимо дали тези елементи са посочени. - person bdoughan; 27.01.2011