我们正在使用Spring(4.1.1。)实现REST API。对于某些HTTP请求,我们希望返回一个没有主体的头作为响应。但是,使用ResponseEntity<Void>似乎无效。通过MockMvc测试调用时,返回406(不可接受)。使用ResponseEntity<String>不带参数的值(new ResponseEntity<String>( HttpStatus.NOT_FOUND ))工作正常。
ResponseEntity<Void>
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 ); } }
测试用例(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)
注意:对于问题4.1.1.RELEASE中提到的版本,这是正确的。
Spring MVC ResponseEntity通过来处理返回值HttpEntityMethodProcessor。
Spring MVC ResponseEntity
HttpEntityMethodProcessor
当ResponseEntity值未设置主体(如你的代码段中的情况)时,将HttpEntityMethodProcessor尝试根据处理程序方法ResponseEntity签名中的返回类型的参数确定响应主体的内容类型@RequestMapping。
ResponseEntity
@RequestMapping
因此对于
public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {
该类型将是Void。HttpEntityMethodProcessor然后将遍历其所有已注册HttpMessageConverter实例,并找到可以为某种Void类型编写主体的实例。根据你的配置,可能找不到,也可能找不到。
HttpMessageConverter
如果确实找到任何内容,则仍然需要确保将使用与请求Accept标头中提供的类型匹配的Content-Type编写相应的正文application/xml。
如果在所有这些检查之后都不HttpMessageConverter存在,Spring MVC将决定它无法产生可接受的响应,因此返回406 Not Acceptable HTTP响应。
使用ResponseEntity<String>,Spring将String用作响应主体并查找StringHttpMessageConverter作为处理程序。并且由于StringHttpMessageHandler可以产生任何媒体类型的内容(Accept标头中提供),因此它将能够处理application/xml你的客户端所请求的内容。
StringHttpMessageConverter
StringHttpMessageHandler
从那以后,Spring MVC已更改为仅如果ResponseEntityNOT中的主体为,则仅返回406 null。如果你使用的是Spring MVC的最新版本,则你不会在原始问题中看到该行为。
ResponseEntityNOT
在iddy85的解决方案中(这似乎暗示ResponseEntity<?>了这一点),人体的类型将推断为Object。如果你的类路径中有正确的库,即。杰克逊(版本> 2.5.0)及其XML扩展,Spring MVC将MappingJackson2XmlHttpMessageConverter可以使用它来application/xml为type 生成Object。他们的解决方案仅在这些条件下有效。否则,由于与我上述相同的原因,它将失败。
ResponseEntity<?>
MappingJackson2XmlHttpMessageConverter
application/xml