spring基本使用(25)-springMVC9-SpringMVC的返回值处理器HandlerMethodReturnValueHandler
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