先上代码:前端
代码1:web
@Controller public class TestController { @RequestMapping(value = "/a") @ResponseBody public Object a() throws Exception { MyResponse myResponse = new MyResponse(); myResponse.code = 200; myResponse.msg = "hello world, 你好世界。"; return myResponse; } @RequestMapping(value = "/b") @ResponseBody public String b() throws Exception { return "hello world, 你好世界。"; } static class MyResponse<T> { public int code; public String msg; public T data; } }
代码2:spring
~ curl -X GET "http://localhost:8080/a" {"code":200,"msg":"hello world, 你好世界。","data":null} ~ curl -X GET "http://localhost:8080/b" hello world, ?????
咱们使用spring mvc开发restful接口的通常模式如代码1第4第11行代码所示,使用@ResponseBody注解一个方法,表示把该方法的返回值写进response的body里。同时声明一个对象,形如MyResponse,做为controller方法的返回值,统一restful接口的结构。代码2第1第2行调用该接口,正常返回中文字符,不乱码。json
若是咱们的返回值不须要最外层的结构,返回给用户的就是我咱们处理后的一个字符串,把这个字符串存放在response的body里返回,那么这个方法瓜熟蒂落的被写成代码1第13第17行的样子。代码2第3第4行是调用这个接口的过程,会出现中文乱码。restful
面对乱码,最直接的解决办法就是查看方法的返回值是如何被写到response的body里的,这就是渲染的过程,咱们知道,spring mvc有两个地方会渲染,上面的状况会在 ServletInvocableHandlerMethod 类里进行渲染,能够参考spring源码解析-web系列(四):九大组件之HandlerAdapter文章里 ServletInvocableHandlerMethod 部分,spring源码解析-web系列(四):九大组件之HandlerAdapter文章代码8第20~第21行执行渲染过程,渲染的代码以下:mvc
代码3 (org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite):app
@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null) { throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName()); } handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } @Nullable private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) { boolean isAsyncValue = isAsyncReturnValue(value, returnType); for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) { if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) { continue; } if (handler.supportsReturnType(returnType)) { return handler; } } return null; }
代码3第5行获取handler,代码3第9行调用handler的handleReturnValue方法渲染返回值。对于咱们的这个方法,获取到的handler为RequestResponseBodyMethodProcessor类型。curl
代码4 (org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue):ide
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest); ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest); this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
RequestResponseBodyMethodProcessor的handleReturnValue方法如代码4所示,它调用了父类的writeWithMessageConverters方法完成渲染。svg
代码5 (org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters):
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { Object outputValue; Class valueType; Object declaredType; if (value instanceof CharSequence) { outputValue = value.toString(); valueType = String.class; declaredType = String.class; } else { outputValue = value; valueType = this.getReturnValueType(value, returnType); declaredType = this.getGenericType(returnType); } if (this.isResourceType(value, returnType)) { outputMessage.getHeaders().set("Accept-Ranges", "bytes"); if (value != null && inputMessage.getHeaders().getFirst("Range") != null) { Resource resource = (Resource)value; try { List<HttpRange> httpRanges = inputMessage.getHeaders().getRange(); outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value()); outputValue = HttpRange.toResourceRegions(httpRanges, resource); valueType = outputValue.getClass(); declaredType = RESOURCE_REGION_LIST_TYPE; } catch (IllegalArgumentException var17) { outputMessage.getHeaders().set("Content-Range", "bytes */" + resource.contentLength()); outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value()); } } } MediaType contentType = outputMessage.getHeaders().getContentType(); Object mediaTypesToUse; if (contentType != null && contentType.isConcrete()) { mediaTypesToUse = Collections.singletonList(contentType); } else { HttpServletRequest request = inputMessage.getServletRequest(); List<MediaType> requestedMediaTypes = this.getAcceptableMediaTypes(request); List<MediaType> producibleMediaTypes = this.getProducibleMediaTypes(request, valueType, (Type)declaredType); if (outputValue != null && producibleMediaTypes.isEmpty()) { throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType); } mediaTypesToUse = new ArrayList(); Iterator var13 = requestedMediaTypes.iterator(); while(var13.hasNext()) { MediaType requestedType = (MediaType)var13.next(); Iterator var15 = producibleMediaTypes.iterator(); while(var15.hasNext()) { MediaType producibleType = (MediaType)var15.next(); if (requestedType.isCompatibleWith(producibleType)) { ((List)mediaTypesToUse).add(this.getMostSpecificMediaType(requestedType, producibleType)); } } } if (((List)mediaTypesToUse).isEmpty()) { if (outputValue != null) { throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); } return; } MediaType.sortBySpecificityAndQuality((List)mediaTypesToUse); } MediaType selectedMediaType = null; Iterator var21 = ((List)mediaTypesToUse).iterator(); while(var21.hasNext()) { MediaType mediaType = (MediaType)var21.next(); if (mediaType.isConcrete()) { selectedMediaType = mediaType; break; } if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) { selectedMediaType = MediaType.APPLICATION_OCTET_STREAM; break; } } HttpMessageConverter converter; GenericHttpMessageConverter genericConverter; label138: { if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); var21 = this.messageConverters.iterator(); while(var21.hasNext()) { converter = (HttpMessageConverter)var21.next(); genericConverter = converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter)converter : null; if (genericConverter != null) { if (((GenericHttpMessageConverter)converter).canWrite((Type)declaredType, valueType, selectedMediaType)) { break label138; } } else if (converter.canWrite(valueType, selectedMediaType)) { break label138; } } } if (outputValue != null) { throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes); } return; } outputValue = this.getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, converter.getClass(), inputMessage, outputMessage); if (outputValue != null) { this.addContentDispositionHeader(inputMessage, outputMessage); if (genericConverter != null) { genericConverter.write(outputValue, (Type)declaredType, selectedMediaType, outputMessage); } else { converter.write(outputValue, selectedMediaType, outputMessage); } if (this.logger.isDebugEnabled()) { this.logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType + "\" using [" + converter + "]"); } } }
代码5有点长,我先补充一点前置知识:HandlerMethodReturnValueHandler(咱们用的是RequestResponseBodyMethodProcessor实现类)使用来处理(渲染)咱们的controller方法的返回值的,可是它也不是直接渲染的,而是借助 HttpMessageConverter 的概念, HttpMessageConverter 的做用是:从request读取数据;把数据写入response。为了灵活通用可扩展, HttpMessageConverter 也是按照spring的套路实现的,每一个 HttpMessageConverter 能够判断本身是否支持某种类型的处理,在调用它的地方有若干个 HttpMessageConverter ,循环这些 HttpMessageConverter ,找到支持的Converter来处理。 HttpMessageConverter 接口定义以下:
代码6 (org.springframework.http.converter.HttpMessageConverter):
public interface HttpMessageConverter<T> { boolean canRead(Class<?> var1, @Nullable MediaType var2); boolean canWrite(Class<?> var1, @Nullable MediaType var2); List<MediaType> getSupportedMediaTypes(); T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException; void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException; }
经过代码6能够发现: HttpMessageConverter 在判断是否能够渲染时须要对象的类型和MediaType两个信息,对象的类型就是咱们的controller方法返回的对象类型,MediaType就是response里的content-type。HttpMessageConverter 的 getSupportedMediaTypes 方法返回这个 HttpMessageConverter 对应的能够写入的content-type的值。
代码5第2行~第85行获取 selectedMediaType ,selectedMediaType就是写进response的content-type。代码2第87第112行获取HttpMessageConverter。代码5第114第126行使用HttpMessageConverter进行渲染。
这里最关键的就是如何获取 selectedMediaType :代码5第39行获取request里header里的 Accept 设置的值,这里取到的值为 * / * 。代码5第40行获取这个controller处理之后写入到response里的content-type的值。获取过程以下:
代码7 (org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.getProducibleMediaTypes):
protected List<MediaType> getProducibleMediaTypes(HttpServletRequest request, Class<?> valueClass, @Nullable Type declaredType) { Set<MediaType> mediaTypes = (Set)request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); if (!CollectionUtils.isEmpty(mediaTypes)) { return new ArrayList(mediaTypes); } else if (this.allSupportedMediaTypes.isEmpty()) { return Collections.singletonList(MediaType.ALL); } else { List<MediaType> result = new ArrayList(); Iterator var6 = this.messageConverters.iterator(); while(true) { while(var6.hasNext()) { HttpMessageConverter<?> converter = (HttpMessageConverter)var6.next(); if (converter instanceof GenericHttpMessageConverter && declaredType != null) { if (((GenericHttpMessageConverter)converter).canWrite(declaredType, valueClass, (MediaType)null)) { result.addAll(converter.getSupportedMediaTypes()); } } else if (converter.canWrite(valueClass, (MediaType)null)) { result.addAll(converter.getSupportedMediaTypes()); } } return result; } } }
代码7第2行,先获取request里的HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE属性,这个属性就是咱们配置在@RequestMapping注解里的produces里的值,在HandlerMapping获取handler时被赋值到request里。若是有值,直接返回;不然代码7第8~第24行,循环全部的 HttpMessageConverter ,使用MediaType为null,判断HttpMessageConverter是否能够渲染这个对象,若是能够渲染,则把HttpMessageConverter支持的content-type返回。
代码5第68行对找到的MediaType进行排序,代码5第71~第85行,获取最合适的MediaType。
渲染的过程分析完了,那么乱码究竟是怎么出现的呢?由于controller的返回值不一样,致使找到的 HttpMessageConverter 不一样,进而致使 MediaType 不一样,因此前端看到的是乱码。
对于代码1第6行的a方法,因为@RequestMapping注解里produces属性为空且返回值为MyResponse类型,在获取producibleMediaTypes时, 支持的 HttpMessageConverter 有MappingJackson2HttpMessageConverter,获取到的producibleMediaTypes为 application/json 和 application/*+json 两个,选择到最优的MediaType为 application/json ,最后会被MappingJackson2HttpMessageConverter渲染,在MappingJackson2HttpMessageConverter中添加defaultCharset,因为MappingJackson2HttpMessageConverter中的defaultCharset为 UTF-8 ,最终content-type为 application/json;charset=UTF-8 。
对于代码1第15行的b方法,因为@RequestMapping注解里produces属性为空且返回值为String类型,在获取producibleMediaTypes时, 支持的 HttpMessageConverter 有 StringHttpMessageConverter 和 MappingJackson2HttpMessageConverter 两个,获取到的producibleMediaTypes为 text/plain 、 * / * 、 application/json 、 application/*+json 四个,选择到最优的MediaType为 text/plain ,最后会被StringHttpMessageConverter渲染,在StringHttpMessageConverter中添加defaultCharset,因为StringHttpMessageConverter中的defaultCharset为 ISO-8859-1 ,最终content-type为 text/plain;charset=ISO-8859-1 。
下面代码能够证实上面的分析:
代码8:
~ curl -i -X GET "http://localhost:8080/a" HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 12 Aug 2019 09:10:10 GMT {"code":200,"msg":"hello world, 你好世界。","data":null} ~ ~ ~ ~ curl -i -X GET "http://localhost:8080/b" HTTP/1.1 200 Content-Type: text/plain;charset=ISO-8859-1 Content-Length: 18 Date: Mon, 12 Aug 2019 09:10:12 GMT hello world, ?????
这个bug的缘由就是方法返回值不一样引发的MediaType不一样,进而致使content-type不一样。 建议咱们在使用的时候指定@RequestMapping注解的produces属性。