SpringMvc流程分析-2

SpringMvc流程分析-2

和上一篇一样,画个图比较方便的说明问题

SpringMvc分析-2(细节)

参数的解析操作

是通过HandlerMethodArgumentResolver来做的,具体的实现方式就得看不同的实现类了,这里着重分析一下@RequestParam注解对应的解析方式(org.springframework.web.method.annotation.RequestParamMethodArgumentResolver),这种写法在Spring中很常见,提供两个方法,一个是support,一个是handle,先看是否支持,在着重解析

  1. 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;
			}
		}
	}
  1. Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)

    总体的思想是:

    1. 确定参数的名字。
    2. 从请求体中获取对应的参数值。
    3. 利用WebDataBinderFactory创建WebBinder,并且调用WebBindingInitializer来自定义WebBinder(这里面就会调用到@InitBinder注解标注的方法)
    4. 利用WebBinder将参数值转换为需要的类型。
    5. 留给子类拓展的方法(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的方法表示支持和处理。

  1. supports

    看类上面或者方法上面是否有ResponseBody。

    	@Override
    	public boolean supportsReturnType(MethodParameter returnType) {
    		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
    				returnType.hasMethodAnnotation(ResponseBody.class));
    	}
    
  2. 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);
    	}
    
  3. writeWithMessageConverters

    这里面的方法太多了,我就挑了重要的说,往响应体中写数据的操作。大体的步骤如下:

    1. 确定返回值类型。
    2. 确定响应头;
    3. 写数据
    
    		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技术,它在渲染的时候需要几个东西。

  1. IContext 上下文对象,可以获取属性(其实就是map)和local
  2. TemplateEngine 模板引擎,用来干活的。
  3. ITemplateResolver 通过它可以加载模板,配置属性。

核心的方法就是 templateEngine.process("模板名字", context对象, “属性”);就可以拿到渲染之后的文本了,也可以直接让它输出到流里面。

对于上面的 ThymeleafViewResolver肯定有对应的View对象(ThymeleafViewAbstractThymeleafView)。

在这里插入图片描述

核心的代码就是上面这个。

异常的处理

异常的处理,在流程图中已经说了。对应的代码在 DispatcherServlet#processDispatchResult 遇到异常之后,通过一系列的操作,最后还是要返回一个ModelAndView对象。很巧妙。这里也会走参数绑定,校验的逻辑,但是这个时候属性就不变了,因为之前已经将ServletWebRequest中的requestActive设置为false,表示该请求已经结束了,所以和模型,session相关的都不会变的。此外,如果异常处理类异常了,异常就会抛出来,下面会解释

异常是通过HandlerExceptionResolver来做处理的,在处理异常的时候得找到对应的处理方法,这里叫作(ExceptionHandlerMethodResolver)得先找当前Controller里面是否有@ExceptionHandler直接,如果没有,就找@ControllerAdvice标注的。对应的代码在ExceptionHandlerExceptionResolver#getExceptionHandlerMethod中。

在这里插入图片描述

  1. 当前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);
    
  2. @ControllerAdvice里面的@ExceptionHandler是什么时候发现的?

    ExceptionHandlerExceptionResolver加载到容器的时候,获取所有的Bean,找到标注了@ControllerAdvice注解方法,创建ExceptionHandlerMethodResolver解析。

在这里插入图片描述

到头来还得使用ExceptionHandlerMethodResolver来做解析

ExceptionHandlerExceptionResolver在SpringMvc中是WebMvcConfigurationSupport中作为配置类加载进来的。

  1. 异常处理类异常了会怎么办

打印异常日志,会将原始的错误返回给服务器,交给服务器来处理

先看,在调用异常处理方法的时候,报错,会直接到Catch块里面,打印异常日志。
在这里插入图片描述

因为上一步返回了null,这里直接抛出原始的错误。

在这里插入图片描述

最后一直抛给了Tomcat

在这里插入图片描述

tomcat设置500,走之前的异常页面。在Springboot中就是/error。

在这里插入图片描述

拦截器的扫描和加载

在Springmvc中配置拦截器是通过WebMvcConfigurer来配置的。实现这个接口,就可以添加拦截器,配置一些乱七八槽的东西。之所以这样是因为SpringMvc提供了一个WebMvcConfigurationSupport,他是一个配置类,他有一个实现类(DelegatingWebMvcConfiguration),它需要WebMvcConfigurer。在自动注入的时候会注入进来。就会将容器中所有的实现了WebMvcConfigurer接口的Bean添加进来,然后在创建RequestMappingHandlerMapping Bean的时候通过RequestMappingHandlerMapping#setInterceptors(Object...)方法设置进来,这样在构建HandlerExecutionChain的时候就可以使用了。

关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值