在上篇SpringMVC 源码分析(七)中,我们了解到客户端请求到服务器后如何经过Spring MVC找到对应的Controller处理方法并反射调用的流程,本篇主要讲一下Spring MVC又是如何处理Controller处理方法返回的结果响应给客户端的。
首先回到反射调用Controller返回的代码处ServletInvocableHandlerMethod#invokeAndHandle,拿到返回值returnValue后,首先判断Controller的处理方法有没有加@ReaponseStatus注解,如果有的话会对其属性进行解析,@ReaponseStatus注解有三个属性(code,value,reason),其中如果reason不为空时会不管程序处理成功与否都会覆盖我们的响应结果,该注解在项目中很少用到,可以不考虑,接下来先对mavContainer中的请求是否被处理的属性标志requestHandled设置为false,该属性主要用于处理视图模型封装与跳转的逻辑,如果Controller类或者方法上有类似@ResponseBody这种需要返回特定格式注解的,就不需要控制跳转,所以该字段一直为false。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//执行调用并返回结果数据
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//如果Controller对应的方法设置了@ResponseStatus,就设置response的status状态,没有就不处理,一般都不需要额外设置的
setResponseStatus(webRequest);
//如果没有返回值就不需要封装,直接返回
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
//如果设置有原因,直接返回
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
//默认为false,如果有@ResponseStatus设置,则为true
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
//解析返回的数据
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
接下来就是对返回结果的封装处理,进入this.returnValueHandlers.handleReturnValue方法中,发现跟解析请求参数this.resolvers.resolveArgument一样的套路模式,就是选择合适的处理器来解析返回值,因为我写的Demo中Controller类上加了@ResponseBody注解,所以该解析任务也就交给了RequestResponseBodyMethodProcessor来处理,(当然我们以前前后端不分离的时候,前端页面资源都在服务器下面,通过springMVC来跳转页面时,用到的处理器就是ViewMethodReturnValueHandler,里面只有将返回值当作页面路径放到mavContainer的viewName属性中,另外再判断下是不是redirect跳转等等我们不做过多的研究),这里只RequestResponseBodyMethodProcessor解析请求参数的代码。
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//同HandlerMethodArgumentResolverComposite参数解析器选择器一样,选择一个合适的响应结果解析器
//因为我们Controller方法有@ResponseBody注解,所以这里选择的解析器仍为RequestResponseBodyMethodProcessor解析器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
//解析返回的记过
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
解析请求参数this.resolvers.resolveArgument源码,具体可以在SpringMVC 源码分析(七)中查看。
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//获取一个支持HandlerMethodArgument解析的的解析器,
//我们Controller处理方法使用@RequestBody接受请求,所以对应RequestResponseBodyMethodProcessor
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
//RequestResponseBodyMethodProcessor开始解析
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
接下来我们再进入RequestResponseBodyMethodProcessor的handleReturnValue方法中查看代码逻辑,只是做了简单的封装处理,重点逻辑在writeWithMessageConverters方法中:
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
//设置请求已处理,不需要SpringMVC再处理页面跳转逻辑
mavContainer.setRequestHandled(true);
//对请求进行二次封装,分别创建请求对象ServletServerHttpRequest和响应对象ServletServerHttpResponse
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
// 将返回值,返回值类型,以及请求响应,使用转换器进行写出
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
进入writeWithMessageConverters方法,该方法比较长,关键位置已经加上注释,根据注释边可以看懂该部分的处理逻辑,主要就是先获取反射调用后返回的值类型和Controller处理器方法真正的类型,以及内容协商原理,从不同的地方选择最合适的浏览器接收类型,后面会放到header中返回,接着再匹配到合适的HttpMessageConverter,并调用write方法进行返回值value与Controller处理器方法真正类型的封装处理。
通过该图可以更清楚的了解内容协商处理。
![](https://img-blog.csdnimg.cn/img_convert/b89d12b0bd5f4efb9c679509d57675d7.png)
源码:
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
Object body;
//我们的返回值value的类型
Class<?> valueType;
//最终要转换写入的数据类型
Type targetType;
//根据value的类型进行绑定
if (value instanceof CharSequence) {
//我的demo返回的是String类型,所以走到这里
body = value.toString();
valueType = String.class;
targetType = String.class;
}
else {
//如果是自定义java类,则走这里
body = value;
valueType = getReturnValueType(body, returnType);
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}
//判断返回值是否为Resource类型的,如果是就处理流数据(stream)
if (isResourceType(value, returnType)) {
outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&
outputMessage.getServletResponse().getStatus() == 200) {
Resource resource = (Resource) value;
try {
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());
body = HttpRange.toResourceRegions(httpRanges, resource);
valueType = body.getClass();
targetType = RESOURCE_REGION_LIST_TYPE;
}
catch (IllegalArgumentException ex) {
outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
}
}
}
//内容协商
//选择合适的接收类型返回给浏览器
MediaType selectedMediaType = null;
//首先从响应头中获取合适的接受类型
MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
if (isContentTypePreset) {
if (logger.isDebugEnabled()) {
logger.debug("Found 'Content-Type:" + contentType + "' in response");
}
//能获取到的话就是用该类型
selectedMediaType = contentType;
}
else {
//如果响应头里面没有合适的接收类型,从请求头中获取可以接收的类型,比如从header中获取
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
//获取服务器可以支持的接收类型
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
if (body != null && producibleTypes.isEmpty()) {
throw new HttpMessageNotWritableException(
"No converter found for return value of type: " + valueType);
}
List<MediaType> mediaTypesToUse = new ArrayList<>();
//循环匹配合适的接收类型
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}
//没有找到合适的接收类型,抛出异常
if (mediaTypesToUse.isEmpty()) {
if (body != null) {
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}
if (logger.isDebugEnabled()) {
logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
}
return;
}
//将合适的媒体类型进行排序,获取最优的返回媒体类型
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Using '" + selectedMediaType + "', given " +
acceptableTypes + " and supported " + producibleTypes);
}
}
// 开始转换返回值的类型,因为demo返回的是String类型,
// 所以匹配到的转换器是:org.springframework.http.converter.StringHttpMessageConverter
// 项目一般使用的就是String和自定义的java类,所以其他的也不用考虑,又兴趣的可以深入了解下
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
//转换特定返回类型,比如自定义的java封装对象。
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
//AbstractHttpMessageConverter
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
if (body != null) {
Set<MediaType> producibleMediaTypes =
(Set<MediaType>) inputMessage.getServletRequest()
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {
throw new HttpMessageNotWritableException(
"No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");
}
throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}
}
进入convert的write方法,此处调用的是父类AbstractGenericHttpMessageConverter的write方法,添加响应设置headers,同时调用抽象方法writeInternal将返回值写出到响应对象中。
public final void write(final T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
//添加header
final HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() {
@Override
public OutputStream getBody() {
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}));
}
else {
//将返回值写出到响应对象中
writeInternal(t, outputMessage);
//刷空输出流,并输出所有被缓存的字节。由于某些流支持缓存功能,该方法将把缓存中所有内容强制输出到流中。
outputMessage.getBody().flush();
}
}
我使用的demo是返回String,所以调用的是StringHttpMessageConverter的writeInternal,进入该方法,可以看到主要是将返回值拷贝到响应流中。
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
HttpHeaders headers = outputMessage.getHeaders();
if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) {
headers.setAcceptCharset(getAcceptedCharsets());
}
// 获取字符集
Charset charset = getContentTypeCharset(headers.getContentType());
// 将返回值拷贝到输出流中
StreamUtils.copy(str, charset, outputMessage.getBody());
}
当然我们也可以返回自定义的java类,那么writeWithMessageConverters方法中的convert为AbstractGenericHttpMessageConverter类,调用writeInternal为子类AbstractJackson2HttpMessageConverter,Spring MVC主要是借助jackson提供的功能将返回值与自定义的java类结合并放入响应流中,有兴趣可以去看下相关源码。
writeInternal方法看完,那么this.returnValueHandlers.handleReturnValue就分析完了,也就是invokeAndHandle分析完了,那么返回上一层调用invokeAndHandle的方法org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod中,因为代码太长,中间省去了一些代码,具体可以看SpringMVC 源码分析(七)。
![](https://img-blog.csdnimg.cn/img_convert/b06ebcc4920d48c995ff663b1c099084.png)
进入getModelAndView,主要处理视图,因为前面调用的是处理@ResponseBody的RequestResponseBodyMethodProcessor类,直接将isRequestHandled设置为true,所以这里直接就返回了null。如果是需要处理视图的,比如我们以前前后端不分离的项目,就需要设置ModelAndView映射关系,以及redirect跳转处理。
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
// 判断是否需要返回视图
// 如果是@RequestBody注解对应的处理器时,返回的为json对象,不需要返回视图,就会设置mavContainer.isRequestHandled()为true
// 后续流程中就不需要视图渲染
if (mavContainer.isRequestHandled()) {
return null;
}
// 需要返回视图的话,例如我们的Controller对应的处理器没有指定返回什么类型,则根据处理器方法返回对应的视图。
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
看完getModelAndView方法后,invokeHandlerMethod也分析完毕,继续返回上一层,最后一直接着org.springframework.web.servlet.DispatcherServlet#doDispatch中的ha.handle方法往下走:
![](https://img-blog.csdnimg.cn/img_convert/30cf8fd8e62740dcb97420c57d12dc9d.png)
applyDefaultViewName顾名思义就是设置默认的视图,查看代码是根据mv是否有视图来设置,因为我们在getModelAndView方法中分析过了,我们通过@ResponseBody对应的处理器来处理,不需要视图,所以mv为null,这里就不需要设置默认视图。
![](https://img-blog.csdnimg.cn/img_convert/f991611d88c449e287b8505e295cccfa.png)
接下来执行mappedHandler.applyPostHandle后置处理,空实现,留给开发自定义实现。
再接下来执行processDispatchResult方法,主要是渲染视图,如果异常会返回异常视图,我们可以进去看下代码
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
//是否展示错误页面标记
boolean errorView = false;
//如果有异常,则设置errorView为true
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 判断当前Controller的@RequestMapping是否需要返回一个视图
// 比如像有@ResponseBody注解这种,不需要返回视图的,mv为null,就不会走进来
if (mv != null && !mv.wasCleared()) {
//渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
//主要用作SPI,一般应用不会走这里
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return;
}
if (mappedHandler != null) {
// 视图处理完毕,调用拦截器对应的Controller的@RequestMapping最终完成的开放接口,用户可以自己实现
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
再到后面就是try-catch中的finally模块,主要是清除之前在线程中设置的信息,以便不影响后续线程的数据。
这个返回过程中有人可能会疑惑,发现处理Controller返回值的handleReturnValue方法无返回值,那么response怎么最终返回给客户端的呢,其实可以沿着代码顺序多仔细观察会发现response其实被封装成一个个对象,可以底层的引用还是response对象,所以最终输出流outputStream还是response的,最终一路返回到tomcat这样的web容器,由web容器返回给客户端。
好啦~Spring MVC源码分析到此结束。