SpringMvc流程分析-2
和上一篇一样,画个图比较方便的说明问题
参数的解析操作
是通过HandlerMethodArgumentResolver
来做的,具体的实现方式就得看不同的实现类了,这里着重分析一下@RequestParam
注解对应的解析方式(org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
),这种写法在Spring中很常见,提供两个方法,一个是support,一个是handle,先看是否支持,在着重解析
- boolean supportsParameter(MethodParameter)
先看是否有@RequestParam注解,如果没有,再看是否有RequestPart注解,如果没有,再看参数类型是否是简单的类型(Java原生的几个类型)。
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
return true;
}
}
else {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
-
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)
总体的思想是:
- 确定参数的名字。
- 从请求体中获取对应的参数值。
- 利用WebDataBinderFactory创建WebBinder,并且调用
WebBindingInitializer
来自定义WebBinder(这里面就会调用到@InitBinder注解标注的方法) - 利用WebBinder将参数值转换为需要的类型。
- 留给子类拓展的方法(handleResolvedValue)。
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // 拿到参数的信息(如果指定没有就取参数的名字) NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); // 确定参数名字 Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name); if (resolvedName == null) { throw new IllegalArgumentException( "Specified name must not resolve to null: [" + namedValueInfo.name + "]"); } // 处理参数。获取值,不同的子类有不同的实现, Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest); if (arg == null) { if (namedValueInfo.defaultValue != null) { arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue); } else if (namedValueInfo.required && !nestedParameter.isOptional()) { handleMissingValue(namedValueInfo.name, nestedParameter, webRequest); } arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType()); } else if ("".equals(arg) && namedValueInfo.defaultValue != null) { arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue); } // 下面做的就是,绑定参数,利用binderFactory来创建WebDataBinder,在创建WebDataBinder的时候会调用标注了@InitBinder注解的方法,来自定义Binder操作 if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name); try { //转换参数的类型, arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter); } catch (ConversionNotSupportedException ex) { throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } catch (TypeMismatchException ex) { throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(), namedValueInfo.name, parameter, ex.getCause()); } // Check for null value after conversion of incoming argument value if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) { handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest); } } // 留给后面的子类的增强方法,对于返回值来说 handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest); return arg; }
方法返回值的处理
是通过HandlerMethodReturnValueHandler
来做处理的,这里着重分析RequestResponseBodyMethodProcessor
,还是同样的方法,对于这种方式,都会提供一个support和handle的方法表示支持和处理。
-
supports
看类上面或者方法上面是否有ResponseBody。
@Override public boolean supportsReturnType(MethodParameter returnType) { return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); }
-
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
@Override public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { // 设置当前请求处理结束了 mavContainer.setRequestHandled(true); // 用她两来包装输入和输出流 ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); }
-
writeWithMessageConverters
这里面的方法太多了,我就挑了重要的说,往响应体中写数据的操作。大体的步骤如下:
- 确定返回值类型。
- 确定响应头;
- 写数据
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) { //利用MessageConvert将消息写出去,这个convert是上一步找到的Convert genericConverter.write(body, targetType, selectedMediaType, outputMessage); } else { ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); } } else { if (logger.isDebugEnabled()) { logger.debug("Nothing to write: null body"); } } return; } } }
这里得特别注意一下MessageConvert和RequestResponseBodyAdviceChain#beforeBodyWrite这两个操作。HttpMessageConverter的实现类很多,之后可以单独的说说。此外beforeBodyWrite的操作真的很奇妙,它可以通过配置配置
ResponseBodyAdvice
,也可以直接利用@ControllerAdvice来完成,很有意思
视图的渲染
通过ViewResolver
确定View之后,之后的实现就不同实现类的不同渲染方式了。这里就不详细的说了
在Springboot中,用的最多的就是Thymeleaf技术,它在渲染的时候需要几个东西。
- IContext 上下文对象,可以获取属性(其实就是map)和local
- TemplateEngine 模板引擎,用来干活的。
- ITemplateResolver 通过它可以加载模板,配置属性。
核心的方法就是 templateEngine.process("模板名字", context对象, “属性”);
就可以拿到渲染之后的文本了,也可以直接让它输出到流里面。
对于上面的 ThymeleafViewResolver
肯定有对应的View对象(ThymeleafView
,AbstractThymeleafView
)。
核心的代码就是上面这个。
异常的处理
异常的处理,在流程图中已经说了。对应的代码在 DispatcherServlet#processDispatchResult
遇到异常之后,通过一系列的操作,最后还是要返回一个ModelAndView
对象。很巧妙。这里也会走参数绑定,校验的逻辑,但是这个时候属性就不变了,因为之前已经将ServletWebRequest
中的requestActive设置为false,表示该请求已经结束了,所以和模型,session相关的都不会变的。此外,如果异常处理类异常了,异常就会抛出来,下面会解释
异常是通过HandlerExceptionResolver
来做处理的,在处理异常的时候得找到对应的处理方法,这里叫作(ExceptionHandlerMethodResolver
)得先找当前Controller里面是否有@ExceptionHandler直接,如果没有,就找@ControllerAdvice标注的。对应的代码在ExceptionHandlerExceptionResolver#getExceptionHandlerMethod
中。
-
当前Controller里面的@ExceptionHandler标注的方法是在什么时候发现的?
在创建
ExceptionHandlerMethodResolver
的时候发现的public ExceptionHandlerMethodResolver(Class<?> handlerType) { for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) { addExceptionMapping(exceptionType, method); } } } public static final MethodFilter EXCEPTION_HANDLER_METHODS = method -> AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
-
@ControllerAdvice里面的@ExceptionHandler是什么时候发现的?
在
ExceptionHandlerExceptionResolver
加载到容器的时候,获取所有的Bean,找到标注了@ControllerAdvice注解方法,创建ExceptionHandlerMethodResolver解析。
到头来还得使用ExceptionHandlerMethodResolver
来做解析
而ExceptionHandlerExceptionResolver
在SpringMvc中是WebMvcConfigurationSupport
中作为配置类加载进来的。
- 异常处理类异常了会怎么办
打印异常日志,会将原始的错误返回给服务器,交给服务器来处理。
先看,在调用异常处理方法的时候,报错,会直接到Catch块里面,打印异常日志。
因为上一步返回了null,这里直接抛出原始的错误。
最后一直抛给了Tomcat
tomcat设置500,走之前的异常页面。在Springboot中就是/error。
拦截器的扫描和加载
在Springmvc中配置拦截器是通过WebMvcConfigurer
来配置的。实现这个接口,就可以添加拦截器,配置一些乱七八槽的东西。之所以这样是因为SpringMvc提供了一个WebMvcConfigurationSupport
,他是一个配置类,他有一个实现类(DelegatingWebMvcConfiguration),它需要WebMvcConfigurer
。在自动注入的时候会注入进来。就会将容器中所有的实现了WebMvcConfigurer接口的Bean添加进来,然后在创建RequestMappingHandlerMapping
Bean的时候通过RequestMappingHandlerMapping#setInterceptors(Object...)
方法设置进来,这样在构建HandlerExecutionChain
的时候就可以使用了。
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。