我正在使用带有Spring Web服务且没有Spring Boot的Spring Framework版本4.1.6。要学习该框架,我正在编写REST API并进行测试以确保从命中端点收到的JSON响应正确。具体来说,我想调整ObjectMapper的PropertyNamingStrategy‘用下划线小写’命名策略使用。
ObjectMapper
PropertyNamingStrategy
我正在使用Spring博客上详细介绍的方法创建一个新方法ObjectMapper,并将其添加到转换器列表中。如下所示:
package com.myproject.config; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import org.springframework.context.annotation.*; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.List; @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { Jackson2ObjectMapperBuilder builder = jacksonBuilder(); converters.add(new MappingJackson2HttpMessageConverter(builder.build())); } public Jackson2ObjectMapperBuilder jacksonBuilder() { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); return builder; } }
然后,运行以下测试(使用JUnit,MockMvc和Mockito)以验证我的更改:
package com.myproject.controller; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.http.MediaType; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.AnnotationConfigWebContextLoader; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; // Along with other application imports... @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(classes = {WebConfig.class}, loader = AnnotationConfigWebContextLoader.class) public class MyControllerTest { @Mock private MyManager myManager; @InjectMocks private MyController myController; private MockMvc mockMvc; @Before public void setup() { MockitoAnnotations.initMocks(this); this.mockMvc = MockMvcBuilders.standaloneSetup(this.myController).build(); } @Test public void testMyControllerWithNameParam() throws Exception { MyEntity expected = new MyEntity(); String name = "expected"; String title = "expected title"; // Set up MyEntity with data. expected.setId(1); // Random ID. expected.setEntityName(name); expected.setEntityTitle(title) // When the MyManager instance is asked for the MyEntity with name parameter, // return expected. when(this.myManager.read(name)).thenReturn(expected); // Assert the proper results. MvcResult result = mockMvc.perform( get("/v1/endpoint") .param("name", name)) .andExpect(status().isOk()) .andExpect((content().contentType("application/json;charset=UTF-8"))) .andExpect(jsonPath("$.entity_name", is(name)))) .andExpect(jsonPath("$.entity_title", is(title))) .andReturn(); System.out.println(result.getResponse().getContentAsString()); } }
但是,这将返回以下响应:
{"id": 1, "entityName": "expected", "entityTitle": "expected title"}
什么时候应该得到:
{"id": 1, "entity_name": "expected", "entity_title": "expected title"}
我有一个实现的WebApplicationInitializer,它会扫描该软件包:
package com.myproject.config; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; public class WebAppInitializer implements WebApplicationInitializer { public void onStartup(ServletContext servletContext) throws ServletException { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.scan("com.myproject.config"); ctx.setServletContext(servletContext); ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); servlet.setLoadOnStartup(1); servlet.addMapping("/"); servletContext.addListener(new ContextLoaderListener(ctx)); } }
使用IntelliJ中的调试器,我可以看到已创建并添加了构建器,但是ObjectMapper实际上并没有使用生成器。我一定想念一些东西,但是我设法找到的所有例子似乎都没有提到那是什么!我尝试消除@EnableWebMvc并实现WebMvcConfigurationSupport,MappingJackson2HttpMessageConverter作为Bean使用,并设置ObjectMapper为Bean无效。
@EnableWebMvc
WebMvcConfigurationSupport
MappingJackson2HttpMessageConverter
任何帮助将不胜感激!请让我知道是否需要其他文件。
谢谢!
编辑: 正在做更多的挖掘,发现了这一点。在该链接中,作者setMessageConverters()在构建MockMvc之前附加了它,并且对作者有效。这样做同样对我有用。但是,由于存储库尚未清除,因此我不确定是否所有功能都可以在生产中使用。当我发现时,我将提交答案。
setMessageConverters()
编辑2: 请参阅答案。
我调查了一下为什么这样做会如此。重申一下,让我的自定义ObjectMapper在我的测试中工作的过程(假定MockMvc是作为独立的创建的)如下:
WebConfig
WebMvcConfigurerAdapter
@Bean
Jackson2ObjectMapperBuilder
CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
@Override
configureMessageConverters()
@ContextConfiguration(classes = { WebConfig.class })
@Autowired
MockMvc
.setMessageConverters()
测试文件:
package com.myproject.controller; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.http.MediaType; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.AnnotationConfigWebContextLoader; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; // Along with other application imports... @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(classes = {WebConfig.class}) public class MyControllerTest { /** * Note that the converter needs to be autowired into the test in order for * MockMvc to recognize it in the setup() method. */ @Autowired private MappingJackson2HttpMessageConverter jackson2HttpMessageConverter; @Mock private MyManager myManager; @InjectMocks private MyController myController; private MockMvc mockMvc; @Before public void setup() { MockitoAnnotations.initMocks(this); this.mockMvc = MockMvcBuilders .standaloneSetup(this.myController) .setMessageConverters(this.jackson2HttpMessageConverter) // Important! .build(); } @Test public void testMyControllerWithNameParam() throws Exception { MyEntity expected = new MyEntity(); String name = "expected"; String title = "expected title"; // Set up MyEntity with data. expected.setId(1); // Random ID. expected.setEntityName(name); expected.setEntityTitle(title) // When the MyManager instance is asked for the MyEntity with name parameter, // return expected. when(this.myManager.read(name)).thenReturn(expected); // Assert the proper results. MvcResult result = mockMvc.perform( get("/v1/endpoint") .param("name", name)) .andExpect(status().isOk()) .andExpect((content().contentType("application/json;charset=UTF-8"))) .andExpect(jsonPath("$.entity_name", is(name)))) .andExpect(jsonPath("$.entity_title", is(title))) .andReturn(); System.out.println(result.getResponse().getContentAsString()); } }
和配置文件:
package com.myproject.config; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import org.springframework.context.annotation.*; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.util.List; @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(jackson2HttpMessageConverter()); } @Bean public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); Jackson2ObjectMapperBuilder builder = this.jacksonBuilder(); converter.setObjectMapper(builder.build()); return converter; } public Jackson2ObjectMapperBuilder jacksonBuilder() { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); return builder; } }
将我生成的WAR文件部署到XAMPP中的Tomcat 7表示正确使用了命名策略。我认为这样做是可行的,因为在独立安装中,除非另有说明,否则始终使用默认的消息转换器集。可以setMessageConverters()在StandAloneMockMvcBuilder.java(版本4.1.6,\org\springframework\test\web\servlet\setup\StandaloneMockMvcBuilder.java)中的函数注释中看到:
\org\springframework\test\web\servlet\setup\StandaloneMockMvcBuilder.java
/** * Set the message converters to use in argument resolvers and in return value * handlers, which support reading and/or writing to the body of the request * and response. If no message converters are added to the list, a default * list of converters is added instead. */ public StandaloneMockMvcBuilder setMessageConverters(HttpMessageConverter<?>...messageConverters) { this.messageConverters = Arrays.asList(messageConverters); return this; }
因此,如果在构建MockMvc的过程中未明确告知MockMvc有关消息转换器的更改,它将不会使用这些更改。