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

Sca*_*Sky 27 java rest spring spring-mvc

我们正在使用Spring实现REST API(4.1.1.).对于某些HTTP请求,我们希望返回一个没有正文作为响应的头.但是,使用ResponseEntity<Void>似乎不起作用.使用MockMvc测试调用时,将返回406(不可接受).使用ResponseEntity<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 );
    }

}
Run Code Online (Sandbox Code Playgroud)

测试用例(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() );

}

}
Run Code Online (Sandbox Code Playgroud)

此堆栈跟踪失败:

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)
Run Code Online (Sandbox Code Playgroud)

Sot*_*lis 28

当你返回ResponseEntity没有正文的a时,Spring使用ResponseEntity返回类型声明中提供的type参数来决定一个body类型.

因此对于

public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {
Run Code Online (Sandbox Code Playgroud)

那种类型Void.然后,Spring将遍历其所有已注册的HttpMessageConverter实例,并找到一个可以为Void类型编写正文的实例.由于没有这样的HttpMessageConverter(对于默认配置),它将决定它不能产生可接受的响应,因此返回406 Not Acceptable HTTP响应.

有了ResponseEntity<String>,Spring将String用作响应体并找到StringHttpMessageConverter处理程序.并且由于StringHttpMessageHandler可以为任何Accepted媒体类型生成内容,因此它将能够处理application/xml客户端请求的内容.

iddy85的解决方案(目前是错误的,但似乎暗示ResponseEntity<?>),身体的类型将被推断为Object.如果类路径中有正确的库,Spring将可以访问HttpMessageConverter可用于生成application/xml类型的XML Object.

  • 嗨Sotirios,谢谢你的回复.请确保:您实际上是在说我必须使用ResponseEntity <String>,对吧? (6认同)

Mar*_*szS 12

对于 Spring 5.2+ 这对我有用:

@PostMapping("/foo")
ResponseEntity<Void> foo(@PathVariable UUID fooId) {
    return fooService.findExam(fooId)
            .map(uri -> ResponseEntity.noContent().<Void>build())
            .orElse(ResponseEntity.notFound().build());
}
Run Code Online (Sandbox Code Playgroud)


iam*_*ddy 9

您的方法实现不明确,请尝试以下操作,稍微编辑您的代码并使用HttpStatus.NO_CONTENTie 204 No Content代替HttpStatus.OK

服务器已完成请求但不需要返回实体主体,并且可能希望返回更新的元信息.响应可以包括实体标题形式的新的或更新的元信息,如果存在,应该与所请求的变体相关联.

对于204,将忽略任何T值,但不会忽略404

  public ResponseEntity<?> 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<T>(HttpStatus.NO_CONTENT);
            } else {
               LOG.debug( "Taxonomy package with key: {0} does NOT exist.", taxonomyKey ); //$NON-NLS-1$
                return new ResponseEntity<T>( HttpStatus.NOT_FOUND );
            }

    }
Run Code Online (Sandbox Code Playgroud)

  • `T` 是类型变量吗?在哪里声明的?你的建议会带来什么不同? (2认同)
  • 我没有改变原始代码的任何戏剧性,而不是建议使用HttpStatus.NO_CONTENT而不是HttpStatus.OK来进行无身反应 (2认同)

ada*_*lev 9

您也可以不指定类型参数,它看起来更清晰,以及Spring在查看文档时的意图:

@RequestMapping(method = RequestMethod.HEAD, value = Constants.KEY )
public ResponseEntity taxonomyPackageExists( @PathVariable final String key ){
    // ...
    return new ResponseEntity(HttpStatus.NO_CONTENT);
}
Run Code Online (Sandbox Code Playgroud)


GKi*_*lin 8

根据Spring 4 MVC ResponseEntity.BodyBuilder和ResponseEntity增强示例,可以将其编写为:

....
   return ResponseEntity.ok().build();
....
   return ResponseEntity.noContent().build();
Run Code Online (Sandbox Code Playgroud)

更新:

如果返回的值Optional有方便的方法,则返回ok()notFound()

return ResponseEntity.of(optional)
Run Code Online (Sandbox Code Playgroud)