2

我正在使用 Spring 4,以及 Spring MVC。

我有以下 POJO 类

@XmlRootElement(name="person")
@XmlType(propOrder = {"id",...})
public class Person implements Serializable {

    …

    @Id
    @XmlElement
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }

    …
}

如果我想通过 Spring MVC 以 XML 格式返回Person列表,我有以下处理程序

@RequestMapping(value="/getxmlpersons/generic", 
        method=RequestMethod.GET, 
        produces=MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public JaxbGenericList<Person> getXMLPersonsGeneric(){
    logger.info("getXMLPersonsGeneric - getxmlpersonsgeneric");
    List<Person> persons = new ArrayList<>();
    persons.add(...);
    …more

    JaxbGenericList<Person> jaxbGenericList = new JaxbGenericList<>(persons);

    return jaxbGenericList;
}

JaxbGenericList 在哪里(基于泛型的类声明

@XmlRootElement(name="list")
public class JaxbGenericList<T> {

    private List<T> list;

    public JaxbGenericList(){}

    public JaxbGenericList(List<T> list){
        this.list=list;
    }

    @XmlElement(name="item")
    public List<T> getList(){
        return list;
    }
}

当我执行 URL 时,我得到以下错误堆栈跟踪

org.springframework.http.converter.HttpMessageNotWritableException: Could not marshal [com.manuel.jordan.jaxb.support.JaxbGenericList@31f8809e]: null; nested exception is javax.xml.bind.MarshalException
 - with linked exception:
[com.sun.istack.internal.SAXException2: class com.manuel.jordan.domain.Person nor any of its super class is known to this context.
javax.xml.bind.JAXBException: class com.manuel.jordan.domain.Person nor any of its super class is known to this context.]

但是如果我有这个(类声明不基于泛型):

@XmlRootElement(name="list")
public class JaxbPersonList {

    private List<Person> list;

    public JaxbPersonList(){}

    public JaxbPersonList(List<Person> list){
        this.list=list;
    }

    @XmlElement(name="item")
    public List<Person> getList(){
        return list;
    }
}

当然还有其他handler方法,如下

@RequestMapping(value="/getxmlpersons/specific", 
        method=RequestMethod.GET, 
        produces=MediaType.APPLICATION_XML_VALUE)
@ResponseBody
public JaxbPersonList getXMLPersons(){
    logger.info("getXMLPersonsSpecific - getxmlpersonsspecific");
    List<Person> persons = new ArrayList<>();
    persons.add(...);
    …more 

    JaxbPersonList jaxbPersonList = new JaxbPersonList(persons);

    return jaxbPersonList;
}

一切正常:

问题,我需要什么额外的配置才能只使用JaxbGenericList

加法一

我没有配置一个marshallerbean,所以我认为Spring在幕后提供了一个。现在根据您的回复,我添加了以下内容:

@Bean
public Jaxb2Marshaller marshaller() {
    Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
    marshaller.setClassesToBeBound(JaxbGenericList.class, Person.class);
    Map<String, Object> props = new HashMap<>();
    props.put(Marshaller.JAXB_FORMATTED_OUTPUT, true);
    marshaller.setMarshallerProperties(props);
    return marshaller;
}

似乎缺少某些东西,因为我“再次”收到:

org.springframework.http.converter.HttpMessageNotWritableException: Could not marshal [com.manuel.jordan.jaxb.support.JaxbGenericList@6f763e89]: null; nested exception is javax.xml.bind.MarshalException
 - with linked exception:
[com.sun.istack.internal.SAXException2: class com.manuel.jordan.domain.Person nor any of its super class is known to this context.
javax.xml.bind.JAXBException: class com.manuel.jordan.domain.Person nor any of its super class is known to this context.]

您是否在 Web 环境中尝试过?似乎我需要告诉 Spring MVC 使用它marshaller,我说似乎是因为其他使用POJO/XML 集合的 URL 工作正常。

我正在使用 Spring 4.0.5,并且 Web 环境是通过 Java Config 配置的

加法二

您的最新版本建议有效

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(marshallingMessageConverter());
}

@Bean
public MarshallingHttpMessageConverter marshallingMessageConverter() {
    MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter();
    converter.setMarshaller(jaxbMarshaller());
    converter.setUnmarshaller(jaxbMarshaller());
    return converter;
}

但现在我的 XML 不是通用的,JSON 代码失败了。

对于我的其他 XML URL http://localhost:8080/spring-utility/person/getxmlpersons/specific

HTTP Status 406 -

type Status report

message

description The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.

已修复添加或更新:

从:

marshaller.setClassesToBeBound(JaxbGenericList.class, Person.class);

至:

marshaller.setClassesToBeBound(JaxbGenericList.class, Person.class, JaxbPersonList.class);

http://localhost:8080/spring-utility/person/getjsonperson但是对于 JSON,再次使用其他 URL

HTTP Status 406 -

type Status report

message

description The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.

似乎我需要为 JSON 添加一个转换器(我之前没有定义),我使用了 Spring 默认值,你能帮我一把吗?

4

1 回答 1

2

在 Spring 中发生这种情况的一种方法是,如果您没有正确配置编组器。以这个独立的为例:

测试应用:

public class TestApplication {
    public static void main(String[] args) throws Exception {
        AbstractApplicationContext context
                = new AnnotationConfigApplicationContext(AppConfig.class);

        Marshaller marshaller = context.getBean(Marshaller.class);
        GenericWrapper<Person> personListWrapper = new GenericWrapper<>();
        Person person = new Person();
        person.setId(1);
        personListWrapper.getItems().add(person);
        marshaller.marshal(personListWrapper, new StreamResult(System.out) );
        context.close();
    }
} 

AppConfig(注意setClassesToBeBound

@Configuration
public class AppConfig {
    @Bean
    public Jaxb2Marshaller marshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setClassesToBeBound(GenericWrapper.class);
        Map<String, Object> props = new HashMap<>();
        props.put(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setMarshallerProperties(props);
        return marshaller;
    }
}

领域

@XmlRootElement
public class Person {

    protected Integer id;

    @XmlElement
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
}
@XmlRootElement
public class GenericWrapper<T> {

    protected List<T> items;

    public GenericWrapper() {}
    public GenericWrapper(List<T> items) {
        this.items = items;
    }

    @XmlElement(name = "item")
    public List<T> getItems() {
        if (items == null) {
            items = new ArrayList<>();
        }
        return items;
    }
}

运行上述应用程序的结果,它将无法编组,异常原因相同

Caused by: javax.xml.bind.JAXBException: class com.stackoverflow.Person nor any of its super class is known to this context.

原因,如果你看的话AppConfig,就是方法setClassesToBeBound。它只包括GenericWrapper类。那么为什么这是一个问题呢?

让我们看看引擎盖下的 JAXB:

我们通过JAXBContext. 通常只用顶级元素创建上下文就可以了

JAXBContext context = JAXBContext.newInstance(GenericWrapper.class);

JAXB 会将与该类关联的所有类拉入上下文。这就是为什么List<Person>有效。Person.class是显式关联的。

但在 的情况下GenericWrapper<T>Person类与GenericWrapper. 但是如果我们明确地将Person类放入上下文中,它就会被找到,并且我们可以使用泛型。

JAXBContext context 
                = JAXBContext.newInstance(GenericWrapper.class, Person.class);

话虽这么说,在引擎盖下,Jaxb2Marshaller使用相同的上下文。回到AppConfig,你可以看到marshaller.setClassesToBeBound(GenericWrapper.class);,它最终与JAXBContext上面的第一个创建相同。如果我们添加,那么我们将具有与第二次创建Person.class相同的配置。JAXBContext

marshaller.setClassesToBeBound(GenericWrapper.class, Person.class);

现在再次运行测试应用程序,它将获得所需的输出。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<genericWrapper>
    <item>
        <id>1</id>
    </item>
</genericWrapper>

说了这么多...

我不知道你是如何设置编组器的配置的,或者框架是否有默认设置。但是您需要掌握编组器,并将其配置为以以下三种方式之一查找类:

  • packagesToScan.
  • contextPath
  • classesToBeBound(就像我在上面所做的那样)

这可以通过Java配置或xml配置来完成



更新(使用 Spring Web)

所以看起来,在 Spring web 环境中,如果你不指定 http 消息转换器,Spring 将使用Jaxb2RootElemenHttpMessageConverter并且只是使用返回类型作为上下文的根元素。还没有检查引擎盖下发生了什么(在源代码中),但我认为问题出在我上面描述的某个地方,Person.class没有被拉到上下文中。

您可以做的是MarshallingHttpMessageConverter在 servlet 上下文中指定您自己的 . 为了让它工作(使用 xml 配置),这就是我添加的

<annotation-driven>
    <message-converters>
        <beans:bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
            <beans:property name="marshaller" ref="jaxbMarshaller"/>
            <beans:property name="unmarshaller" ref="jaxbMarshaller"/>
        </beans:bean>
    </message-converters>
</annotation-driven>

<beans:bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
    <beans:property name="classesToBeBound">
        <beans:list>
            <beans:value>com.stackoverflow.jaxb.domain.JaxbGenericList</beans:value>
            <beans:value>com.stackoverflow.jaxb.domain.Person</beans:value>
        </beans:list>
    </beans:property>
</beans:bean>

您可以使用 Java 配置实现相同的功能,如@EnableWebMcvAPI 中所示,您可以在其中扩展WebMvcConfigurerAdapter和覆盖configureMessageConverters,并在那里添加消息转换器。就像是:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            converters.add(marshallingMessageConverter());
    }

    @Bean
    public MarshallingHttpMessageConverter marshallingMessageConverter() {
        MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter();
        converter.setMarshaller(jaxbMarshaller());
        converter.setUnmarshaller(jaxbMarshaller());
        return converter;
    }

    @Bean 
    public Jaxb2Marshaller jaxbMarshaller() {
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setClassesToBeBound(JaxbGenericList.class, Person.class);
        Map<String, Object> props = new HashMap<>();
        props.put(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setMarshallerProperties(props);
        return marshaller;
    }
}

使用类似的控制器方法:

@Controller
public class TestController {

    @RequestMapping(value = "/people", 
                    method = RequestMethod.GET, 
                    produces = MediaType.APPLICATION_XML_VALUE)
    @ResponseBody
    public JaxbGenericList<Person> getXMLPersonsGeneric() {
        JaxbGenericList<Person> personsList = new JaxbGenericList<Person>();
        Person person1 = new Person();
        person1.setId(1);
        personsList.getItems().add(person1);
        return personsList;
    }
}

我们得到我们想要的结果

在此处输入图像描述

于 2014-09-20T11:29:20.517 回答