带有JAXB的Spring MVC,基于Generic类的List响应

时间:2014-09-19 17:47:46

标签: spring spring-mvc jaxb spring-4 spring-oxm

我正在使用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;
    }

    …
}

如果我想以XML格式通过Spring MVC返回 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.]

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

@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;
    }
}

和其他处理程序方法当然如下

@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

一起安心工作的额外配置

添加一个

我没有配置marshaller bean,所以我认为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.]

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

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

加两个

您的最新版本建议有效

@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网址 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);

但对于JSON,其他网址http://localhost:8080/spring-utility/person/getjsonperson再次

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默认值,你能帮我一把吗?

1 个答案:

答案 0 :(得分:2)

如果你没有正确配置编组器,那么在Spring中可能会发生这种情况。以此为例:

TestApplication:

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与JAXB互动。通常,只需使用顶级元素(如

)创建上下文即可
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>

所有这一切......

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

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

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



更新(使用Spring网站)

所以看来,在Spring Web环境中,如果你没有指定http消息转换器,Spring将使用Jaxb2RootElemenHttpMessageConverter并只使用返回类型作为上下文的根元素。还没有检查引擎盖下的内容(在源代码中),但我认为问题出在我上面描述的某处,Person.class没有被引入上下文。

您可以做的是在servlet上下文中指定您自己的MarshallingHttpMessageConverter。为了使它工作(使用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配置实现相同的功能,如@EnableWebMcv API中所示,您可以在其中扩展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;
    }
}

我们得到了我们想要的结果

enter image description here