我正在使用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默认值,你能帮我一把吗?
答案 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 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;
}
}
我们得到了我们想要的结果
: