Spring:使用 ResponseEntity<Void> 返回空 HTTP 响应不起作用

2022-08-31 15:37:14

我们正在用Spring(4.1.1.)实现一个REST API。对于某些 HTTP 请求,我们希望返回一个没有正文的 head 作为响应。但是,使用似乎不起作用。当使用测试调用时,将返回 406(不可接受)。不带参数值 () 的使用工作正常。ResponseEntity<Void>MockMvcResponseEntity<String>new ResponseEntity<String>( HttpStatus.NOT_FOUND )

方法:

@RequestMapping( method = RequestMethod.HEAD, value = Constants.KEY )
public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {

    LOG.debug( "taxonomyPackageExists queried with key: {0}", key ); //$NON-NLS-1$

    final TaxonomyKey taxonomyKey = TaxonomyKey.fromString( key );

    LOG.debug( "Taxonomy key created: {0}", taxonomyKey ); //$NON-NLS-1$

    if ( this.xbrlInstanceValidator.taxonomyPackageExists( taxonomyKey ) ) {

        LOG.debug( "Taxonomy package with key: {0} exists.", taxonomyKey ); //$NON-NLS-1$

        return new ResponseEntity<Void>( HttpStatus.OK );

    } else {

        LOG.debug( "Taxonomy package with key: {0} does NOT exist.", taxonomyKey ); //$NON-NLS-1$

        return new ResponseEntity<Void>( HttpStatus.NOT_FOUND );
    }

}

测试用例(TestNG):

public class TaxonomyQueryControllerTest {

private XbrlInstanceValidator   xbrlInstanceValidatorMock;
private TaxonomyQueryController underTest;
private MockMvc                 mockMvc;

@BeforeMethod
public void setUp() {
    this.xbrlInstanceValidatorMock = createMock( XbrlInstanceValidator.class );
    this.underTest = new TaxonomyQueryController( this.xbrlInstanceValidatorMock );
    this.mockMvc = MockMvcBuilders.standaloneSetup( this.underTest ).build();
}

@Test
public void taxonomyPackageDoesNotExist() throws Exception {
    // record
    expect( this.xbrlInstanceValidatorMock.taxonomyPackageExists( anyObject( TaxonomyKey.class ) ) ).andStubReturn(
            false );

    // replay
    replay( this.xbrlInstanceValidatorMock );

    // do the test
    final String taxonomyKey = RestDataFixture.taxonomyKeyString;

    this.mockMvc.perform( head( "/taxonomypackages/{key}", taxonomyKey ).accept( //$NON-NLS-1$
            MediaType.APPLICATION_XML ) ).andExpect( status().isNotFound() );

}

}

使用此堆栈跟踪失败:

FAILED: taxonomyPackageDoesNotExist
java.lang.AssertionError: Status expected:<404> but was:<406>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)
at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:652)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:153)
at de.zeb.control.application.xbrlstandalonevalidator.restservice.TaxonomyQueryControllerTest.taxonomyPackageDoesNotExist(TaxonomyQueryControllerTest.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:84)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:714)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
at org.testng.TestRunner.privateRun(TestRunner.java:767)
at org.testng.TestRunner.run(TestRunner.java:617)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:334)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291)
at org.testng.SuiteRunner.run(SuiteRunner.java:240)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1224)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1149)
at org.testng.TestNG.run(TestNG.java:1057)
at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:111)
at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.java:204)
at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:175)

答案 1

注意:对于问题 4.1.1.RELEASE 中提到的版本,情况确实如此。

Spring MVC 通过 处理返回值。ResponseEntityHttpEntityMethodProcessor

当值没有设置正文时(如代码段中的情况),则会尝试通过处理程序方法签名中返回类型的参数化来确定响应正文的内容类型。ResponseEntityHttpEntityMethodProcessorResponseEntity@RequestMapping

所以对于

public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {

该类型将为 。 然后循环遍历其所有已注册的实例,并找到一个可以为类型编写正文的实例。根据您的配置,它可能会或可能不会找到任何配置。VoidHttpEntityMethodProcessorHttpMessageConverterVoid

如果它确实找到了任何内容,它仍然需要确保相应的正文将使用与请求标头中提供的类型匹配的内容类型(在您的情况下)进行编写。Acceptapplication/xml

如果在所有这些检查之后,不存在这样的响应,Spring MVC将决定它不能产生可接受的响应,因此返回406不可接受的HTTP响应。HttpMessageConverter

使用 ,Spring 将用作响应正文并查找作为处理程序。由于可以为任何媒体类型(在标头中提供)生成内容,因此它将能够处理客户端请求的内容。ResponseEntity<String>StringStringHttpMessageConverterStringHttpMessageHandlerAcceptapplication/xml

此后,Spring MVC 已更改为仅当响应实体中的正文不为 null 时才返回 406。如果您使用的是最新版本的 Spring MVC,则不会在原始问题中看到该行为。


iddy85 的解决方案中,这似乎暗示了 ,身体的类型将被推断为 。如果您的类路径中有正确的库,即。Jackson(版本>2.5.0)及其XML扩展,Spring MVC将可以访问它可以用来为该类型生成。他们的解决方案仅在这些条件下有效。否则,它将由于我上面描述的相同原因而失败。ResponseEntity<?>ObjectMappingJackson2XmlHttpMessageConverterapplication/xmlObject


答案 2

根据 Spring 4 MVC ResponseEntity.BodyBuilder 和 ResponseEntity Enhancements Example,它可以写成:

....
   return ResponseEntity.ok().build();
....
   return ResponseEntity.noContent().build();

更新:

如果返回值为 ,则有方便的方法,返回或:Optionalok()notFound()

return ResponseEntity.of(optional)