带有JAXB的Spring MVC,基于泛型类的列表响应 [英] Spring MVC with JAXB, List response based on a Generic class

查看:104
本文介绍了带有JAXB的Spring MVC,基于泛型类的列表响应的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Spring 4和Spring MVC.

I am working with Spring 4, together with 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格式返回的列表,我有以下处理程序

If I want return a list of Person through Spring MVC in XML format, I have the following handler

@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所在的位置(基于泛型的类声明)

Where JaxbGenericList is (class declaration based on Generics)

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

执行网址时,出现以下错误堆栈跟踪

When I execute the URL, I get the following error stack trace

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.]

但是但是,如果我有此声明(不是基于泛型的类声明):

But If I have this (class declaration not based on 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;
    }
}

和其他 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

加法一

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

I did not configure a marshaller bean, so I think Spring behind the scenes has provided one. Now according with your reply I have added the following:

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

似乎丢失了一些东西,因为我再次收到"

Something seems missing because I receive 'again':

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仍然可以正常工作.

Have you tried in a Web Environment? Seems I need tell Spring MVC use that marshaller, I said seems because other URL working with not collections of POJO/XML works fine yet.

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

I am working with Spring 4.0.5 and Web environment is configured through 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代码失败了.

But now my XML not generic and JSON code fails.

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

For my other 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.

已修复添加或更新的问题:

It has been fixed adding or updating:

来自:

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

收件人:

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

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

But for JSON, other URL http://localhost:8080/spring-utility/person/getjsonperson again

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的默认值,请给我帮忙吗?

Seems I need add a converter for JSON (I did not define before), I have used the Spring defaults, could you give me a hand please?

推荐答案

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

One way this can happen in Spring is if you don't have the marshaller configured properly. Take for example this standalone:

TestApplication:

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)

AppConfig (notice the 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;
    }
}

运行上述应用程序的结果将无法进行封送,与异常的原因相同

Result running the above application, it'll fail to marshal, with the same cause of the exception

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

如果查看AppConfig,则原因是方法setClassesToBeBound.它仅包含GenericWrapper类.那么为什么这是一个问题呢?

The cause, if you look at the AppConfig, is the method setClassesToBeBound. It only includes the GenericWrapper class. So why is this a problem?

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

Let's look at JAXB under the hood:

我们通过JAXBContext与JAXB进行交互.通常,只用

We interact with JAXB through the JAXBContext. Normally it's alright to just create the context with the top level element like

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

JAXB会将与该类关联的所有类拉入上下文.这就是List<Person>起作用的原因. Person.class已显式关联.

JAXB will pull into the context all classes associated with the class. That's why List<Person> works. Person.class is explicitly associated.

但是对于GenericWrapper<T>Person类与GenericWrapper无关.但是,如果我们将Person类显式放入上下文中,则会找到它,并且可以使用泛型.

But in the case of GenericWrapper<T>, the Person class is nowhere related to the GenericWrapper. But if we explicitly put Person class into the context, it'll be found, and we can use the generics.

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

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

That being said, under the hood, Jaxb2Marshaller uses this same context. Going back to the AppConfig, you can see marshaller.setClassesToBeBound(GenericWrapper.class);, which is ultimately the same as the first JAXBContext creation above. If we add Person.class, then we would have the same configuration as the second JAXBContext creation.

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

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

Now run the test application again, and it'll get the desired output.

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

所有所说的...

我不知道您如何设置编组器的配置,或者框架是否具有默认设置.但是您需要掌握编组器,并配置它以三种方式之一找到类:

All that being said...

I don't know how you've set up your configuration of the marshaller, or if the framework has a default set up. But you need to get a hold of the marshaller, and configure it to find the classes in one of three ways:

  • packagesToScan.
  • contextPath
  • classesToBeBound(就像我上面所做的一样)
  • packagesToScan.
  • contextPath
  • or classesToBeBound (like I've done above)

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

This can be done through Java configuration or xml configuration

如此看来,在Spring网络环境中,如果您未指定http消息转换器,Spring将使用

So it seems, in a Spring web environment, if you don't specify the http message converter, Spring will use Jaxb2RootElemenHttpMessageConverter and just use the return type as the root element for the context. Haven't checked what's going on under the hood (in the source), but I would assume that the problem lies somewhere in what I described above, where the Person.class is not being pulled into the context.

您可以做的是指定您自己的

What you can do is specify your own MarshallingHttpMessageConverter, in the servlet context. To get it to work (with xml config), this is what I added

<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配置实现相同的目标,如

You could achieve the same with Java confguration, as seen in the @EnableWebMcv API, where you extend WebMvcConfigurerAdapter and override configureMessageConverters, and add the message converter there. Something like:

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

使用类似的控制器方法:

Using similar controller method:

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

我们得到想要的结果

:

这篇关于带有JAXB的Spring MVC,基于泛型类的列表响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆