1、什么是返回值处理器?它的作用是啥?在请求的什么阶段工作?
答:在SpringMVC中返回值处理器用接口HandlerMethodReturnValueHandler表示。它的作用就是可以操作HandlerMethod也就是我编写的Controller中的某方法执行的返回值,至于怎么操作就是看这个返回值处理器的业务了。返回值处理器的工作时机是在Controller中方法返回后就立即执行。
2、HandlerMethodReturnValueHandler接口定义以及类图关系
public interface HandlerMethodReturnValueHandler { /** * Whether the given {@linkplain MethodParameter method return type} is * supported by this handler. * @param returnType the method return type to check * @return {@code true} if this handler supports the supplied return type; * {@code false} otherwise */ 是否支持当前的returnType返回值类型,MethodParameter这个类里面封装了被执行的Controller中本次请求的方法Method实例 + 入参类型 + 入参上的注解列表等信息,有了这些数据,返回值处理器就能判断自己需要处理什么类型的方法返回值。 boolean supportsReturnType(MethodParameter returnType); /** * Handle the given return value by adding attributes to the model and * setting a view or setting the * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true} * to indicate the response has been handled directly. * @param returnValue the value returned from the handler method * @param returnType the type of the return value. This type must have * previously been passed to {@link #supportsReturnType} which must * have returned {@code true}. * @param mavContainer the ModelAndViewContainer for the current request * @param webRequest the current request * @throws Exception if the return value handling results in an error */ 处理返回值的业务,入参是Controller方法的返回值returnValue,返回值类型描述,ModelAndViewContainer实例,还有就是请求实例。 void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception; }类图站面比较大,就展示部分重要的返回值处理器吧:
3、返回值处理器HandlerMethodReturnValueHandler在整个请求中的执行点:
我们知道在RequestMappingHandlerAdapter中的invokeHandlerMethod方法里面是整个请求的核心方法,在这个方法里面会将我们的HandlerMethod实例包装成一个ServletInvocableHandlerMethod的时候,然后执行这个可执行的处理器方法,源码片段如下:
1、将我们的HandlerMethod实例包装成一个ServletInvocableHandlerMethod实例。 ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); ... 2、设置这个可执行处理器方法的返回值处理器列表设置为RequestMappingHandlerAdapter里面配置的返回值处理器列表。 invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); ... 3、执行处理器方法。 invocableMethod.invokeAndHandle(webRequest, mavContainer);接着来到ServletInvocableHandlerMethod 的 invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs)方法:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { 1、先执行Controller方法,得到返回值,如果是void方法,返回值是null。 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); 2、设置请求的响应状态。 setResponseStatus(webRequest); 3、如果返回值是null if (returnValue == null) { 3.1 如果请求是没有被修改过的 || 响应状态不为空 || mavContainer实例的requestHandled是 true这个属性很重要,它决定了是否需要进行视图解析,同时它也标记着请求已经被处理器结束,如果为true 后续的视图解析器就不会解析解析!!!!!! if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { 满足条件就设置mavContainer的请求以被处理器字段为true,表示已被处理。 mavContainer.setRequestHandled(true); 然后直接结束方法,这就是返回值是null的处理方式。 return; } } 4、如果响应状态实例的reason字段是有值的,这个是什么意思呢?比如我们在业务代码里面自行设置的响应状态信息,那么到此处的时候就不会走返回值处理器的逻辑。 else if (StringUtils.hasText(getResponseStatusReason())) { 满足条件就设置mavContainer的请求以被处理器字段为true,表示已被处理。 mavContainer.setRequestHandled(true); 然后直接结束方法。 return; } 4、如果返回值不为null 且响应状态实例里面没有reason,那就不管之前做过什么设置,先覆盖mavContainer实例的requestHandled属性为false。 mavContainer.setRequestHandled(false); try { 5、寻找支持当前返回值类型的返回值处理器,对返回值进行处理。 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; } }这个就是返回值处理器在整个请求期间的执行点。
4、ViewNameMethodReturnValueHandler返回值处理器原理剖析:
这个返回值处理器支持的是方法的返回值类型是void 或者是CharSequence类型,这个类型有很多的实现,比如String就是其中一个。
案例:
@RequestMapping("test/save") public String test(@Validated(value = HumaSaveGroup.class) Huma huma, BindingResult bindingResult) { huma.setAge(18); return "register"; //这个register是一个viewName,比如jsp的名称 }我们来到ViewNameMethodReturnValueHandler的handleReturnValue(...)方法:
@Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { 1、判断返回值的类型是否是CharSequence类型 if (returnValue instanceof CharSequence) { 转成字符串表示viewName String viewName = returnValue.toString(); 设置mavContainer的viewName=viewName,在后续视图解析器工作的时候会使用到。 mavContainer.setViewName(viewName); 如果是带有"redirect:"重定向表示的viewName,那就设置mavContainer的重定向场景=true。 if (isRedirectViewName(viewName)) { mavContainer.setRedirectModelScenario(true); } } 2、如果返回值不是CharSequence类型就抛出UnsupportedOperationException异常。 else if (returnValue != null){ // should not happen throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } }这就是ViewNameMethodReturnValueHandler的实现原理。
5、RequestResponseBodyMethodProcessor返回值处理器原理剖析:
这个返回值处理器的适用场景是Controller类上标注了@ResponseBody注解 || Controller的当前执行的请求方法上标注了@ResponseBody注解注解。
案例:
@RequestMapping("user2") @ResponseBody private MyResponse <String> user1(@RequestBody @Validated Huma huma, BindingResult bindingResult) { int i = 2; if (i == 1) { throw new BusinessException(101, "BusinessException名称不能为空!"); } return MyResponse.buildSuccess("register success"); }我们来到RequestResponseBodyMethodProcessor的handleReturnValue(...)方法:
@Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { 1、先设置mavContainer的requestHandled属性=true,这样的话后续的视图解析器就不会工作了,所 以我们在写rest接口的时候不需要配置一个视图解析器也是能正常工作的。 mavContainer.setRequestHandled(true); 2、构建ServletServerHttpRequest请求实例 ServletServerHttpRequest inputMessage = createInputMessage(webRequest); 3、构建ServletServerHttpResponse响应实例 ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // Try even with null return value. ResponseBodyAdvice could get involved. 4、这里至关重要,这里会将返回值经过HttpMessageConverter将返回值经过处理,然后写入到响应体body中。例如将返回值序列化成json字符串,然后将自字符穿写入到responseBody中。 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }下面来分析writeWithMessageConverters(...)实现:
protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { Object outputValue; Class<?> valueType; Type declaredType; 1、如果返回值是CharSequence类型,那就转成String类型。 if (value instanceof CharSequence) { outputValue = value.toString(); valueType = String.class; declaredType = String.class; } else { outputValue = value; valueType = getReturnValueType(outputValue, returnType); declaredType = getGenericType(returnType); } HttpServletRequest request = inputMessage.getServletRequest(); 2、获取请求的媒体类型列表,在请求是不设置的话将会返回返回一个"*/*"表示通配。 List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request); 3、获取将产生的媒体类型列表,如application/json等,默认会添加很多类型。这里的意思就是响应体你的内容类型是啥,这个是需要确定的。 List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType); 4、如果返回值不为空且将产生的媒体类型是空,那就抛出异常。 if (outputValue != null && producibleMediaTypes.isEmpty()) { throw new IllegalArgumentException("No converter found for return value of type: " + valueType); } 5、分析到可用的媒体类型。 Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>(); for (MediaType requestedType : requestedMediaTypes) { for (MediaType producibleType : producibleMediaTypes) { if (requestedType.isCompatibleWith(producibleType)) { compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType)); } } } 6、如果分析下来没有可用的媒体类型就抛出HttpMediaTypeNotAcceptableException异常,这个错误是比较常见的。 if (compatibleMediaTypes.isEmpty()) { if (outputValue != null) { throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); } return; } List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes); 7、按权重将媒体类型进行排序,也就是说我们可以设置多个媒体类型,并给它们都分配权重,在此处会使用其权重正排序。 MediaType.sortBySpecificityAndQuality(mediaTypes); 8、选择一个媒体类型。 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; } } 9、如果选到了一个可用的媒体类型,那就按此媒体类型进行响应体的输出。 if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); 10、选者合适的HttpMessageConvert进行响应体数据输出。 for (HttpMessageConverter<?> messageConverter : this.messageConverters) { 11、如果是常用的HttpMessageConverter。 if (messageConverter instanceof GenericHttpMessageConverter) { 12、那就判断其是否能够将返回值按照选择的媒体类型进行响应输出。 if (((GenericHttpMessageConverter) messageConverter).canWrite( declaredType, valueType, selectedMediaType)) { 13、如果可以那就先调用所有ResponseBodyAdvice类型的beforeBodyWrite方法,这里跟在入参解析的时候,读requestBody之前的通知是一个道理。 outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage); 14、如果需要输出的值不为空(输出的值也就是返回值,一般情况下。) if (outputValue != null) { addContentDispositionHeader(inputMessage, outputMessage); 15、使用当前的GenericHttpMessageConverteri将返回值按照媒体类型写入到响应体中!!!!!!!!此处很重要。 ((GenericHttpMessageConverter) messageConverter).write( outputValue, declaredType, selectedMediaType, outputMessage); if (logger.isDebugEnabled()) { logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType + "\" using [" + messageConverter + "]"); } } 16、写完响应体立即结束当前函数。 return; } } else if (messageConverter.canWrite(valueType, selectedMediaType)) { outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) messageConverter.getClass(), inputMessage, outputMessage); if (outputValue != null) { addContentDispositionHeader(inputMessage, outputMessage); ((HttpMessageConverter) messageConverter).write(outputValue, selectedMediaType, outputMessage); if (logger.isDebugEnabled()) { logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType + "\" using [" + messageConverter + "]"); } } return; } } } if (outputValue != null) { throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes); } }这就是RequestResponseBodyMethodProcessor返回值处理器的原理。
6、下一节我们分析SpringMVC的视图解析器ViewResolver
