上一篇文章讲述了最简单的mvc:annotation-driven,这次就要说说@ResponseBody注解,很明显这个注解就是将方法的返回值作为reponse的body部分。我们进一步分析下这个过程涉及到的内容,首先就是方法返回的类型,可以是字节数组、字符串、对象引用等,将这些返回类型以什么样的内容格式(即response的content-type类型,同时还要考虑到客户端是否接受这个类型)存进response的body中返回给客户端是一个问题,对于这个过程的处理都是靠许许多多的HttpMessageConverter转换器来完成的,这便是本章要讲的内容。
常用的content-type类型有:text/html、text/plain、text/xml、application/json、application/x-www-form-urlencoded、image/png等,不同的类型,对body中的数据的解析也是不一样的。
我们的@ResponseBody可以指定content-type,打开ResponseBody注释,我们可以看到这两个属性consumes和produces,它们就是用来指定request的content-type和response的content-type的。都可以接收一个或者多个,用法注释中已经给出了说明。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
String[] consumes() default {};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
String[] produces() default {};
当request的content-type不在consumes指定的范围内,则这个request就不会匹配到这个方法。produces 同时指定了方法的返回值将以什么样的content-type写入response的body中。如果这个属性进行了配置下文在获取服务器端指定的content-type就是所配置的值,否则则会获取默认的所有content-type
当我们对@ResponseBody什么都没有配置时,SpringMVC便启用默认的策略帮我们自动寻找一种最佳的方式将方法的返回值写入response的body中。
接下来,我们就需要探究SpringMVC是如何处理这一过程的。先说说我的方式,就是调试,当方法执行完返回后,看DispatcherServlet的doDispatch方法的代码:
-
-
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
-
-
-
String method = request.getMethod();
-
boolean isGet = "GET".equals(method);
-
if (isGet || "HEAD".equals(method)) {
-
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
-
if (logger.isDebugEnabled()) {
-
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
-
}
-
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
-
return;
-
}
-
}
-
-
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
-
return;
-
}
-
-
try {
-
-
-
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这里是适配器进行调度我们的handler地方,由于我们使用的是注解,所以对应的适配器是RequestMappingHandlerAdapter,通过一步步的函数调用,最终找到我们的关注重点到RequestMappingHandlerAdapter的方法invokeHandleMethod,具体实现逻辑:
-
private ModelAndView invokeHandleMethod(HttpServletRequest request,
-
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
-
-
ServletWebRequest webRequest = new ServletWebRequest(request, response);
-
-
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
-
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
-
ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
-
-
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
-
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
-
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
-
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
-
-
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
-
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
-
-
final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
-
asyncManager.setTaskExecutor(this.taskExecutor);
-
asyncManager.setAsyncWebRequest(asyncWebRequest);
-
asyncManager.registerCallableInterceptors(this.callableInterceptors);
-
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
-
-
if (asyncManager.hasConcurrentResult()) {
-
Object result = asyncManager.getConcurrentResult();
-
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
-
asyncManager.clearConcurrentResult();
-
-
if (logger.isDebugEnabled()) {
-
logger.debug("Found concurrent result value [" + result + "]");
-
}
-
requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
-
}
-
-
-
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
-
-
if (asyncManager.isConcurrentHandlingStarted()) {
-
return null;
-
}
-
-
return getModelAndView(mavContainer, modelFactory, webRequest);
-
}
继续深入看下这个requestMappingMethod.invokeAndHandle方法:
-
-
-
-
-
-
-
-
-
public final void invokeAndHandle(ServletWebRequest webRequest,
-
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
-
-
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
-
-
setResponseStatus(webRequest);
-
-
if (returnValue == null) {
-
if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
-
mavContainer.setRequestHandled(true);
-
return;
-
}
-
}
-
else if (StringUtils.hasText(this.responseReason)) {
-
mavContainer.setRequestHandled(true);
-
return;
-
}
-
-
mavContainer.setRequestHandled(false);
-
-
try {
-
-
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
-
}
-
catch (Exception ex) {
-
if (logger.isTraceEnabled()) {
-
logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
-
}
-
throw ex;
-
}
-
}
mavContainer是ModelAndViewContainer类型,主要存储着model信息和view信息,它的一个属性requestHandled为true表示response直接处理不需要view的解决方案(即是需要返回一个视图的)。这里的mavContainer.setRequestHandled(false)只是初始时默认采用view的解决方案。
继续看this.returnValueHandlers.handleReturnValue具体内容:
-
-
-
-
-
@Override
-
public void handleReturnValue(
-
Object returnValue, MethodParameter returnType,
-
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
-
throws Exception {
-
-
HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
-
Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
-
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
-
}
继续看下它是如何找到合适的HandlerMethodReturnValueHandler的
-
-
-
-
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
-
for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
-
if (logger.isTraceEnabled()) {
-
logger.trace("Testing if return value handler [" + returnValueHandler + "] supports [" +
-
returnType.getGenericParameterType() + "]");
-
}
-
if (returnValueHandler.supportsReturnType(returnType)) {
-
return returnValueHandler;
-
}
-
}
-
return null;
-
}
遍历所有的已注册的HandlerMethodReturnValueHandler,然后调用他们的supportsReturnType方法来判断他们各自是否支持这个返回值类型,通过调试发现会有13个HandlerMethodReturnValueHandler,之后再说这些数据是在什么时候哪个地方注册的。列举下常用的:
ModelAndViewMethodReturnValueHandler:支持返回值是ModelAndView类型的
ModelMethodProcessor:支持返回值是Model的
ViewMethodReturnValueHandler:支持返回值是View
HttpEntityMethodProcessor:支持返回值是HttpEntity
RequestResponseBodyMethodProcess:支持类上或者方法上含有@ResponseBody注解的
ViewNameMethodReturnValueHandler:支持返回类型是void或者String
所以我们想扩展的话,就可以自定实现一个HandlerMethodReturnValueHandler,然后在初始化时注册进去(这个过程后面再说)。言归正转,对于本工程RequestResponseBodyMethodProcess是支持的,所以它将被作为HandlerMethodReturnValueHandler返回,继续执行上面的handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest)方法,我们来看下RequestResponseBodyMethodProcess具体的处理过程:
-
@Override
-
public void handleReturnValue(Object returnValue, MethodParameter returnType,
-
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
-
throws IOException, HttpMediaTypeNotAcceptableException {
-
-
mavContainer.setRequestHandled(true);
-
if (returnValue != null) {
-
writeWithMessageConverters(returnValue, returnType, webRequest);
-
}
-
}
方法writeWithMessageConverters的具体内容为:
-
protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
-
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
-
throws IOException, HttpMediaTypeNotAcceptableException {
-
-
Class<?> returnValueClass = returnValue.getClass();
-
HttpServletRequest servletRequest = inputMessage.getServletRequest();
-
-
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
-
-
-
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);
-
-
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
-
for (MediaType requestedType : requestedMediaTypes) {
-
for (MediaType producibleType : producibleMediaTypes) {
-
if (requestedType.isCompatibleWith(producibleType)) {
-
compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
-
}
-
}
-
}
-
if (compatibleMediaTypes.isEmpty()) {
-
throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
-
}
-
-
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
-
MediaType.sortBySpecificityAndQuality(mediaTypes);
-
-
MediaType selectedMediaType = null;
-
for (MediaType mediaType : mediaTypes) {
-
if (mediaType.isConcrete()) {
-
selectedMediaType = mediaType;
-
break;
-
}
-
else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
-
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
-
break;
-
}
-
}
-
-
if (selectedMediaType != null) {
-
selectedMediaType = selectedMediaType.removeQualityValue();
-
for (HttpMessageConverter<?> messageConverter :
-
-
-
this.messageConverters) {
-
if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
-
((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
-
if (logger.isDebugEnabled()) {
-
logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
-
messageConverter + "]");
-
}
-
return;
-
}
-
}
-
}
-
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
-
}
获取客户端的content-type,只需解析Accept头字段即可,获取服务器端指定的content-type则分两种情况,第一种情况为:你在@RequestMapping中指定了produces的content-type类型(会将这一信息存进request的属性中,属性名为HandlerMapping接口名+‘.producibleMediaTypes‘)如果没指定,则第二种情况:获取所有的已注册的messageConverter,获取它们所有的支持的content-type类型,并且过滤掉那些不支持returnValueClass的类型。然后在这两组List<MediaType>
requestedMediaTypes和producibleMediaTypes中进行比较匹配(这里的比较规则也挺多的,涉及到q值,有兴趣你们可以总结下),选出一个最合适的content-type,至此有了返回值类型returnValueClass和要写进reponseBody的content-type类型,然后就是要找到一个支持这两者的HttpMessageConverter,已注册的HttpMessageConverter如下:
ByteArrayHttpMessageConverter:支持返回值类型为byte[],content-type为application/octet-stream,*/*
StringHttpMessageConverter:支持的返回值类型为String,content-type为 text/plain;charset=ISO-8859-1,*/*
ResourceHttpMessageConverter:支持的返回值类型为Resource,content-type为 */*
SourceHttpMessageConverter:支持的返回值类型为DomSource,SAXSource,Source,StreamSource,content-type为application/xml,text/xml,application/*+xml
MappingJacksonHttpMessageConverter:判断返回值能否被格式化成json,content-type为 application/json,application/*+json
AllEncompassingFormHttpMessageConverter:支持的返回值类型为MultiValueMap,content-type为application/x-www-form-urlencoded,multipart/form-data
对于我们的工程来说,返回类型为String,选出来的最合适的content-type是text/html,并且StringHttpMessageConverter的*/*是兼容任意类型的,所以StringHttpMessageConverter会被选中,然后将返回值以text/html形式写进response的body中。
顺便说下对于content-length是这样获取的:
首先从指定的content-type(本工程即text/html)中获取字符集,若能获取到则使用该字符集,若获取不到则使用默认的字符集,对于本工程来说,text/html不像application/json;charset=utf-8那样含有字符集,所以将会使用StringHttpMessageConverter默认的字符集
-
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
-
-
private final Charset defaultCharset;
-
-
private final List<Charset> availableCharsets;
-
-
private boolean writeAcceptCharset = true;
-
- 再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow