带有JAXB的Spring MVC,基于泛型类的列表响应 [英] Spring MVC with JAXB, List response based on a Generic class
问题描述
我正在使用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-JAXB参考文档
如此看来,在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>
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屋!