Spring 升级到新 spring 版本后 MockMvc UnsupportedOperationException

Spring MockMvc UnsupportedOperationException after upgrading to new spring version

我正在从 Spring 3.2.3.RELEASE 迁移到 Spring 4.2.0.RELEASE。 我被卡住了,因为现有的测试开始失败。我的代码看起来像(简化):

@WebAppConfiguration
@ContextConfiguration(classes = [TestConfig.class])
class MvcTest extends Specification {

    @Autowired
    WebApplicationContext context
    MockMvc mockMvc

    @Subject
    MyController controller

    def setup() {
        controller = new MyController()
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build()
    }

    def "should not allow to save an invalid entity"() {
    when:

    def result = mockMvc.perform(post("/people")
                                         .content('''{
                                            "name": "",
                                            "age": 21,
                                            "sex": 1
                                         }''')
                                         .contentType(MediaType.APPLICATION_JSON)
                                         .accept(MediaType.APPLICATION_JSON))

    then:
    result.andDo(print())
          .andExpect(status().isBadRequest())
          .andExpect(jsonPath('$.message').value("Name cannot be empty."))
    }
}

MyController.java

@Controller
public class MyController {

    @RequestMapping(method = RequestMethod.POST, 
            value = "/people", 
            produces = "application/json", 
            consumes = "application/json")
    @ResponseBody
    public Response create(@RequestBody @Valid Person person) {
        //save person to repository. Debugger is not even entering this line...
    }
}

Person.java:

public class Person {

    private int age;

    @NotEmpty(message = "Name cannot be empty.")
    private String name;

    @NotNull
    @JsonSerialize(using = SexTypeSerializer.class)
    @JsonDeserialize(using = SexTypeDeserializer.class)
    private Sex sex;

    //getters, setters, other methods
}

TestConfig.groovy

@EnableWebMvc
@Configuration
class TestConfig {

    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        def objectMapper = new ObjectMapper()
        def jacksonModule = new SimpleModule()
        jacksonModule.addDeserializer(Sex.class, new SexTypeDeserializer())
        jacksonModule.addSerializer(Sex.class, new SexTypeSerializer())

        objectMapper.registerModule(jacksonModule)
        objectMapper
    }
}

堆栈跟踪:

java.lang.UnsupportedOperationException
at org.springframework.test.web.servlet.setup.StubWebApplicationContext$StubBeanFactory.createBean(StubWebApplicationContext.java:369)
at org.springframework.http.converter.json.SpringHandlerInstantiator.deserializerInstance(SpringHandlerInstantiator.java:68)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.deserializerInstance(DefaultDeserializationContext.java:111)
at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findDeserializerFromAnnotation(BasicDeserializerFactory.java:1436)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.constructSettableProperty(BeanDeserializerFactory.java:765)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.addBeanProps(BeanDeserializerFactory.java:544)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:270)
at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:168)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:401)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:350)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:263)
at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:243)
at com.fasterxml.jackson.databind.deser.DeserializerCache.hasValueDeserializerFor(DeserializerCache.java:193)
at com.fasterxml.jackson.databind.DeserializationContext.hasValueDeserializerFor(DeserializationContext.java:344)
at com.fasterxml.jackson.databind.ObjectMapper.canDeserialize(ObjectMapper.java:2035)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.canRead(AbstractJackson2HttpMessageConverter.java:151)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:187)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:148)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:125)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:78)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:162)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:129)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:111)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:806)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:729)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:688)

还提供了自定义序列化程序,并且可以与以前版本的 spring 一起正常工作。我认为配置有问题,因为调用了这样的方法:

StubWebApplicationContext.java

@Override
public <T> T createBean(Class<T> beanClass) {
    throw new UnsupportedOperationException();
}

看起来应该使用一些其他的 createBean 实现。 有什么想法吗?

这是 Spring Framework 4.1.3 中引入的重大更改。

我已经创建了一个错误报告来确保这个问题得到解决:

https://jira.spring.io/browse/SPR-13375

感谢您提醒我们注意此事!

顺便说一句,当你使用MockMvcBuilders.standaloneSetup()时,不需要通过@ContextConfiguration和[=13加载一个ApplicationContext =].如果你分析你的测试 class,你会发现你实际上什至没有使用你有 @AutowiredWebApplicationContext 到你的测试中。因此,您可以安全地删除所有该配置。

还请注意,您的 TestConfig 未在 MockMvc 中使用 ,因为您正在使用 MockMvcBuilders.standaloneSetup() 来测试控制器的实例直接地。换句话说,您的自定义 SexTypeDeserializer 未在这样配置的测试中使用。如果您想继续使用独立设置,您可以使用自定义配置的 ObjectMapper(连同您的自定义序列化程序等)配置 MappingJackson2HttpMessageConverter,并将其传递给 StandaloneMockMvcBuilder通过其 setMessageConverters(...) 方法。

作为替代方案,您可以考虑使用 MockMvcBuilders.webAppContextSetup(context) 以便在您的测试中实际使用您的 SexTypeDeserializer 以及其余的生产 Spring MVC 配置。