2

我不确定这是否是我的错误配置,是对通过@ModelAttribute自动 JSON 内容转换可以完成的工作的误解,还是 Spring 或 Jackson 中的错误。当然,如果结果是后者,我会向适当的人提出问题。

我在将 a 添加@ModelAttribute到控制器的处理程序方法时遇到了问题。该方法的目的是公开一个从表单或先前提交中填充的 bean,但我可以在不实际将数据提交到 bean 的情况下重现该问题。

我正在使用 Spring mvc-showcase 示例。它目前使用的是 Spring 3.1,但我第一次遇到并且能够在我的 3.0.5 设置中重现此问题。mvc-showcase 示例使用了一个非常标准的 servlet-context.xml:

servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->

    <!-- Enables the Spring MVC @Controller programming model -->
    <annotation-driven conversion-service="conversionService">
        <argument-resolvers>
            <beans:bean class="org.springframework.samples.mvc.data.custom.CustomArgumentResolver"/>
        </argument-resolvers>
    </annotation-driven>

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory -->
    <resources mapping="/resources/**" location="/resources/" />

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>

    <!-- Imports user-defined @Controller beans that process client requests -->
    <beans:import resource="controllers.xml" />

    <!-- Only needed because we install custom converters to support the examples in the org.springframewok.samples.mvc.convert package -->
    <beans:bean id="conversionService" class="org.springframework.samples.mvc.convert.CustomConversionServiceFactoryBean" />

    <!-- Only needed because we require fileupload in the org.springframework.samples.mvc.fileupload package -->
    <beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

</beans:beans>

文件中controllers.xml引用的只是为根路径设置相关的组件扫描和视图控制器。相关片段如下。

控制器.xml

<!-- Maps '/' requests to the 'home' view -->
<mvc:view-controller path="/" view-name="home"/>

<context:component-scan base-package="org.springframework.samples.mvc" />

我试图提供的测试 bean 是一个非常简单的 POJO。

TestBean.java

package org.springframework.samples.mvc.test;

public class TestBean {
    private String testField = "test@example.com";

    public String getTestField() {
        return testField;
    }

    public void setTestField(String testField) {
        this.testField = testField;
    }

}

最后是控制器,这也很简单。

测试控制器.java

package org.springframework.samples.mvc.test;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("test/*")
public class TestController {

    @ModelAttribute("testBean")
    public TestBean getTestBean() {
        return new TestBean();
    }

    @RequestMapping(value = "beanOnly", method = RequestMethod.POST)
    public @ResponseBody
    TestBean testBean(@ModelAttribute("testBean") TestBean bean) {
        return bean;
    }

    @RequestMapping(value = "withoutModel", method = RequestMethod.POST)
    public @ResponseBody
    Model testWithoutModel(Model model) {
        model.addAttribute("result", "success");
        return model;
    }

    @RequestMapping(value = "withModel", method = RequestMethod.POST)
    public @ResponseBody
    Model testWithModel(Model model, @ModelAttribute("testBean") TestBean bean) {
        bean.setTestField("This is the new value of testField");
        model.addAttribute("result", "success");
        return model;
    }

}

如果我通过映射路径调用控制器/mvc-showcase/test/beanOnly,我会得到 bean 的 JSON 表示,正如预期的那样。调用withoutModel处理程序会传递Model与调用关联的 Spring 对象的 JSON 表示。它在返回值中包含来自初始声明的隐含@ModelAttribute,但该方法无法使用该 bean。例如,如果我希望处理表单提交的结果并返回 JSON 响应消息,那么我需要该属性。

最后一种方法添加了@ModelAttribute,这就是麻烦出现的地方。调用/mvc-showcase/test/withModel会导致异常。

在我的 3.0.5 安装中,由于缺少 FormattingConversionService 的序列化程序而导致出现 JsonMappingException。在 3.1.0 示例中,该异常是由 DefaultConversionService 缺少序列化程序引起的。我将在此处包括 3.1 例外;即使路径有点不同,它似乎也有相同的根本原因。

3.1 org.codehaus.jackson.map.JsonMappingException

org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.springframework.format.support.DefaultFormattingConversionService and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.validation.support.BindingAwareModelMap["org.springframework.validation.BindingResult.testBean"]->org.springframework.validation.BeanPropertyBindingResult["propertyAccessor"]->org.springframework.beans.BeanWrapperImpl["conversionService"])
    at org.codehaus.jackson.map.ser.StdSerializerProvider$1.failForEmpty(StdSerializerProvider.java:89)
    at org.codehaus.jackson.map.ser.StdSerializerProvider$1.serialize(StdSerializerProvider.java:62)
    at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:272)
    at org.codehaus.jackson.map.ser.BeanSerializer.serializeFields(BeanSerializer.java:175)
    at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:147)
    at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:272)
    at org.codehaus.jackson.map.ser.BeanSerializer.serializeFields(BeanSerializer.java:175)
    at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:147)
    at org.codehaus.jackson.map.ser.MapSerializer.serializeFields(MapSerializer.java:207)
    at org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:140)
    at org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:22)
    at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:315)
    at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:242)
    at org.codehaus.jackson.map.ObjectMapper.writeValue(ObjectMapper.java:1030)
    at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.writeInternal(MappingJacksonHttpMessageConverter.java:153)
    at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:181)
    at org.springframework.web.servlet.mvc.method.annotation.support.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:121)
    at org.springframework.web.servlet.mvc.method.annotation.support.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:101)
    at org.springframework.web.servlet.mvc.method.annotation.support.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:81)
    at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:64)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter.invokeHandlerMethod(RequestMappingHandlerMethodAdapter.java:505)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter.handleInternal(RequestMappingHandlerMethodAdapter.java:468)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
    at 
...

那么,是否有一些我遗漏的配置应该允许杰克逊转换器正确处理从@ModelAttribute方法签名中的处理程序派生的响应?如果没有,关于这更可能是 Spring 错误还是 Jackson 错误有什么想法吗?在这一点上,我倾向于春天。

4

1 回答 1

1

它看起来像一个 Spring 配置问题,当序列化为 JSON 时,它DefaultFormattingConversionService是空的,如果 bean 为空,Jackson(默认情况下)将抛出异常,请参见FAIL_ON_EMPTY_BEANS特性文档。但我不清楚为什么 bean 是空的。

如果您设置为 false,它应该可以FAIL_ON_EMPTY_BEANS工作,但仍然不能真正解释为什么它首先会发生。

DefaultFormattingConversionService是 3.1 的新功能 - 它扩展了 FormattingConversionService,它解释了 3.0.5 和 3.1 之间的不同异常。

我认为这不是 Jackson 的问题,尽管 Jackson 的新版本 ( 1.8.0 )仅在 3 天前发布,因此您也可以尝试。

我将尝试在本地重现此内容。

于 2011-04-23T10:41:44.953 回答