在现在愈来愈崇尚开箱即用的阶段,不少细节被隐藏在了身后,特别是开始使用SpringBoot以后,更多的自动配置,让咱们方便的同时,不少时候也让咱们更加深刻的思考。html
本篇文章就来了解一下遇到比较多的ResponseBody流程相关的问题。前端
Spring处理ResponseBody流程相关的类和接口主要有下面7个:java
RequestMappingHandlerMapping RequestMappingHandlerAdapter HandlerMethodArgumentResolver HandlerMethodReturnValueHandler RequestResponseBodyMethodProcessor AbstractMessageConverterMethodProcessor HttpMessageConverter
RequestMappingHandlerMapping是一个HandlerMapping,简单的来讲它帮咱们找到和请求URL匹配的Controller中@RequestMapping注解的方法。web
RequestMappingHandlerAdapter能够看做是Controller中@RequestMapping注解的方法的一个适配器,处理了一些请求和返回的细节,例如参数注入,返回值处理。spring
HandlerMethodArgumentResolver用于处理Controller中@RequestMapping注解的方法参数 HandlerMethodReturnValueHandler用于处理Controller中@RequestMapping注解的方法返回值json
RequestResponseBodyMethodProcessor实现了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler,用于处理RequestBody、ResponseBody注解。后端
@RestController这个类注解会自动为方法添加上@ResponseBody,因此在@RestController注解的Controller中不须要再显示的添加@ResponseBody注解。app
HttpMessageConverter用于request请求到Controller方法的参数类型,Controller方法的返回值到response之间的转换。ide
例如前端的请求参数转换为RequestBody注解的参数,后端Controller方法的返回的类转换为前端须要的json、xml等实际就是经过HttpMessageConverter完成的。函数
能够经过下面的方法看一下大体Spring容器中有哪些类,检查有没有出现上面提到的类:
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class ApplicationHolder implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { String[] names = applicationContext.getBeanDefinitionNames(); for(String name : names){ System.out.println(name); } } }
知道了前面的内容,咱们如今重点关注一下HttpMessageConverter就能够了。 HttpMessageConverter是RequestMappingHandlerAdapter建立的,有兴趣能够看一下RequestMappingHandlerAdapter的构造函数。
AnnotationDrivenBeanDefinitionParser解析xml配置文件的message-converters也会添加。
关于HttpMessageConverter添加有兴趣能够本身看一下相关源码,这里不详细介绍,咱们要介绍的是在SpringBoot中要如何定制HttpMessageConverter。
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private HttpMessageConverters messageConverters; @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { List<HttpMessageConverter<?>> httpMessageConverters = this.messageConverters.getConverters(); httpMessageConverters.forEach(System.out::println); converters.addAll(httpMessageConverters); // FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); // FastJsonConfig fastJsonConfig = new FastJsonConfig(); // fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); // List<MediaType> fastMediaTypes = new ArrayList<>(); // fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); // fastConverter.setSupportedMediaTypes(fastMediaTypes); // fastConverter.setFastJsonConfig(fastJsonConfig); // converters.add(fastConverter); } }
经过WebMvcConfigurer的configureMessageConverters方法,咱们就能够自由的查看有那些HttpMessageConverter,也能够很容易添加很删除。
例如咱们不想使用默认的MappingJackson2HttpMessageConverter,咱们就能够把它删除了,而后添加一个FastJsonHttpMessageConverter。
若是还不满意,甚至能够本身建立一个HttpMessageConverters,替换掉默认的:
@Bean public HttpMessageConverters customConverters(){ StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); ByteArrayHttpMessageConverter byteArrayHttpMessageConverter = new ByteArrayHttpMessageConverter(); SourceHttpMessageConverter<Source> sourceSourceHttpMessageConverter = new SourceHttpMessageConverter<>(); AllEncompassingFormHttpMessageConverter allEncompassingFormHttpMessageConverter = new AllEncompassingFormHttpMessageConverter(); return new HttpMessageConverters(stringHttpMessageConverter ,byteArrayHttpMessageConverter,sourceSourceHttpMessageConverter,allEncompassingFormHttpMessageConverter); }
或者咱们懒得改,只想配置一下默认的Jackson的行为:
@Bean public ObjectMapper jsonMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper mapper = builder.createXmlMapper(false).build(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return mapper; }
下面的类放在SpringBoot工程中,执行最后的测试类。
import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; @XmlRootElement public class Result<T> implements Serializable { private static final long serialVersionUID = -1; /** * 成功与否,客户端快速判断 */ private boolean status = false; /** * 状态码,方便快速定位问题 */ private int code; /** * 提示信息 */ private String msg; /** * 数据 */ private T data; public Result() { } public Result(boolean status, int code, String msg) { this.status = status; this.code = code; this.msg = msg; } public Result(boolean status, int code, String msg, T data) { this.status = status; this.code = code; this.msg = msg; this.data = data; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
public final class ResultHelper { private static final boolean DEFAULT_SUCCESS_STATUS = true; private static final boolean DEFAULT_FAIL_STATUS = false; private static final int DEFAULT_SUCCESS_CODE = 200; private static final int PARAM_ERROR_CODE = 400; private static final int DEFAULT_ERROR_CODE = 600; private static final String DEFAULT_SUCCESS_MESSAGE = "success"; private static final String DEFAULT_ERROR_MESSAGE = "fail"; private static final String PARAM_ERROR_MESSAGE = "参数错误"; public static <T> Result getDefaultSuccessResult(T data){ return new Result(DEFAULT_SUCCESS_STATUS,DEFAULT_SUCCESS_CODE,DEFAULT_SUCCESS_MESSAGE,data); } public static Result getDefaultErrorResult(){ return new Result(DEFAULT_FAIL_STATUS, DEFAULT_ERROR_CODE, DEFAULT_ERROR_MESSAGE); } public static Result getParamErrorResult(){ return new Result(DEFAULT_FAIL_STATUS,PARAM_ERROR_CODE,PARAM_ERROR_MESSAGE); } public static Result getErrorResult(int code,String message){ return new Result(DEFAULT_FAIL_STATUS, code, message); } }
import org.curitis.common.Result; import org.curitis.common.ResultHelper; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/rrb") public class RRBController { @RequestMapping("/hello") public Result hello(){ return ResultHelper.getParamErrorResult(); } }
import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpHeaders; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 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.ResultHandler; import org.springframework.web.context.WebApplicationContext; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @SpringBootTest public class RRBControllerTest { @Autowired protected WebApplicationContext wac; protected MockMvc mockMvc; @Before public void setUp(){ this.mockMvc = webAppContextSetup(this.wac).build(); } @Test public void json() throws Exception { mockMvc.perform(post("/rrb/hello")) .andExpect(status().isOk()) .andDo(print()) .andReturn(); } @Test public void xml() throws Exception { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add(HttpHeaders.ACCEPT, "application/xml;charset=UTF-8"); mockMvc.perform(post("/rrb/hello") .headers(httpHeaders) .param("page","1") .param("pageSize","20") ) .andDo(new ResultHandler() { @Override public void handle(MvcResult result) throws Exception { System.out.println(result.getResponse().getContentAsString()); } }) .andReturn(); } }
执行上面的测试类,咱们能够看到咱们可以获得不一样的输出,json会获得json字符串,xml会获得xml字符串。
只是由于咱们请求头的accept不一样。
注意,要使用Jaxb2RootElementHttpMessageConverter须要在返回的实体类上添加@XmlRootElement注解。
具体的代码就不详细说了,说一下流程和关键代码,感兴趣的朋友能够本身调试。
在Controller中的方法return以后,若是方法有@ResponseBody注解,那么就会来到RequestResponseBodyMethodProcessor的handleReturnValue。
handleReturnValue会调用AbstractMessageConverterMethodProcessor的writeWithMessageConverters方法。
断点打到writeWithMessageConverters方法中,须要跟踪那个看那个,不用被其余的类和流程绕晕了。
writeWithMessageConverters方法中很重要的逻辑就是找MediaType,由于要根据MediaType来找HttpMessageConverter。
首先从Response的Content-Type判断,若是有,说明服务端是很是清楚要返回什么类型,因此就能够直接肯定。
若是没有,就会根据request去找,若是没有特殊配置就是看Header中的Accept,涉及的类是HeaderContentNegotiationStrategy。
找到客户端须要什么还不算完,由于重要的不是你要什么,而是要看我有什么。
因此还要找应用支持哪些MediaType,基本就是去HttpMessageConverters中的遍历全部HttpMessageConverter支持的类型。
找到支持的MediaType会排序,怎么排序呢?
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
咱们看请求头,常常会看到上面的内容,q=0.9的部分就能够能够看做是权重,值越大优先级越高。 例如上面application/xml;q=0.9,/;q=0.8就表示xml比all类型优先级要高。
若是全部的HttpMessageConverter支持的MediaType都不匹配,那么就会获得下面的异常:
HttpMessageNotWritableException:No converter found for return value of type
SpringBoot默认会使用MappingJackson2HttpMessageConverter,若是咱们不想使用其余json converter,那么咱们能够经过添加一个ObjectMapper来定制Jackson。
@Bean public ObjectMapper jsonMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper mapper = builder.createXmlMapper(false).build(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return mapper; }
关于Jackson的配置,能够参考:Jackson最经常使用配置与注解