springboot系列--web相关知识探索八

时间:2024-11-21 14:38:21

一、前言

        web相关知识系列从1-7,完成了从一个请求从浏览器发出,是如何适配后端接口,请求参数是如何与接口参数进行绑定,再到接口处理完数据后,返回值是如何返回给浏览器以及如何与浏览器接受类型进行适配的,从而形成了一个闭环,web相关知识探索七中主要是研究接口方法使用了注解@ResponseBody返回值是如何转为浏览器能够接收的对应数据类型的,本章主要研究的是视图解析原理流程。

二、视图解析原理

一、测试用例

类上面使用的是@Controller注解,而不是@RestController

    @PostMapping("/test/login")
    public String login(@RequestBody Person person, HttpSession session,Model model){
        if (Objects.nonNull(person) && StrUtil.isNotBlank(person.getUserName())
                && "123456".equalsIgnoreCase(person.getPwd())){
            session.setAttribute("loginUser",person);
            return "redirect:/index.html";
        }else{
            model.addAttribute("msg","账号密码不匹配");
            return "login";
        }
    }

二、原理

        web相关知识系列7中由于使用了@ResponseBody,被底层源码中的

ServletInvocableHandlerMethod这个对象的invokeAndHandle这个方法中的直接将数据写出

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 这里选择使用哪个处理器处理接口返回来的数据使用了@ResponseBody会匹配到 //RequestResponseBodyMethodProcessor这个对象
    HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    } else {
        handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }
}

RequestResponseBodyMethodProcessor对象中的handleReturnValue

方法匹配上,直接将数据转为浏览器能够接受的类型,然后通过方法

@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);

   // Try even with null return value. ResponseBodyAdvice could get involved.
   writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

将数据直接写出去。

一、公共逻辑 

在HandlerMethodReturnValueHandlerComposite类中的handleReturnValue方法开始出现差别。

 public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    // 这里开始选择处理返回值的handler,之前是使用@ResponseBody注解,所以这里会选择到 RequestResponseBodyMethodProcessor这个处理类进行处理 
      HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
        if (handler == null) {
            throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
        } else {
            // 这里就是调用通过判定的类对接口返回值的处理方法
            handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
        }
    }

二、使用了 @ResponseBody注解的处理逻辑

        @ResponseBody这个注解,对应RequestResponseBodyMethodProcessor这个处理类型中的

public boolean supportsReturnType(MethodParameter returnType) {
    return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class);
}

这个方法,进行判断是否支持解析返回值。而这个方法的判断条件就是有没有使用ResponseBody这个注解。

        通过了这个类的判定方法,接下来就会调用这个类对接口返回数据的处理。也就回到之前研究的逻辑了。具体不详细叙述。

   public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
        // 使用了视图容器的只有这行代码,也就是之后都没有用到视图容器
        mavContainer.setRequestHandled(true);
        ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
        ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
        // 在这里就将数据写出到返回域中
        this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    }

三、未使用@ResponseBody的处理逻辑 

        未使用 @ResponseBody这个注解,对应ViewNameMethodReturnValueHandler这个类的处理逻辑。首先会调用他的判定方法,判断是否支持当前接口返回数据的处理。

public boolean supportsReturnType(MethodParameter returnType) {
    Class<?> paramType = returnType.getParameterType();
    return Void.TYPE == paramType || CharSequence.class.isAssignableFrom(paramType);
}

        这里的判断逻辑就是返回值是Void类型的,以及返回值是字符串类型的都能够支持。之后会调用他对返回数据的处理方法。也就是handleReturnValue这个方法。

下面是处理返回值方法handleReturnValue的具体逻辑。

    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
           // 如果返回值是字符串将进入下面逻辑
         if (returnValue instanceof CharSequence) {
            // 将返回值变为字符串
            String viewName = returnValue.toString();
            // 放到 ModelAndViewContainer 容器中
            mavContainer.setViewName(viewName);
            // 这里会继续判断是不是重定向,判断逻辑就是 PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:") 这个。这就是为什么我们如果要重定向的话需要再返回值处写死redirect:这个字符串的原因。只有识别到了这个字符串才会进行处理。
            if (this.isRedirectViewName(viewName)) {
            // 如果是重定向,设置标识为true
                mavContainer.setRedirectModelScenario(true);
            }
        } else if (returnValue != null) {
            throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
        }

    }

 当公共逻辑方法

invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);

里面的逻辑执行完,会进入到下面的处理流程中。RequestMappingHandlerAdapter类里面的invokeHandlerMethod方法

 



RequestMappingHandlerAdapter类中的getModelAndView方法
@Nullable
	private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

		// 更新model,mavContainer对想里面放入了Person对象。方法的参数是一个自定义类型对象(从请求参数中确定的),会重新放在 ModelAndViewContainer 中华
		modelFactory.updateModel(webRequest, mavContainer);
		if (mavContainer.isRequestHandled()) {
			return null;
		}
		// 这个model就是接口参数中的model对象
		ModelMap model = mavContainer.getModel();
		// 将接口返回的数据,也就是viewName和model一起封装成一个ModelAndView对象
		ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
		if (!mavContainer.isViewReference()) {
			mav.setView((View) mavContainer.getView());
		}
		// 判断model是不是RedirectAttributes类型,也就是说接口参数中的model可以是RedirectAttributes类型的参数
		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;
	}

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler. 这里就是实际执行的handler,也就是前面一直研究的内容,这里没有使用@ResponseBody注解后,当前测试用了返回了ModelAndView对象,包含了重定向地址
                // 任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                // 这里如果防线mv对象不为空,但是没有view对象,就会设置一个默认的view对象
                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            // 这里处理派发结果(页面该如何响应)
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

下面是处理派发结果逻辑 


// 处理派发接口,视图解析的真正逻辑就在这里
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		boolean errorView = false;

		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);
			}
		}

		// Did the handler return a view to render? 方法直接回进入到这里,上面是出现异常才会走的,ModelAndView不为空且没有被清理
		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.");
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			// Exception (if any) is already handled..
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}




	// 将进行页面渲染逻辑
	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Determine locale for request and apply it to the response. 国际化处理
		Locale locale =
				(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
		response.setLocale(locale);

		// 这个对象是一个接口来的
		View view;
		String viewName = mv.getViewName();
		if (viewName != null) {
			// We need to resolve the view name.这里将去解析视图名,传入视图名,model对象以及请求参数得到视图对象View,这个对象定义了如何去渲染页面。
			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
			if (view == null) {
				throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
						"' in servlet with name '" + getServletName() + "'");
			}
		}
		else {
			// No need to lookup: the ModelAndView object contains the actual View object.
			view = mv.getView();
			if (view == null) {
				throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
						"View object in servlet with name '" + getServletName() + "'");
			}
		}

		// Delegate to the View object for rendering.
		if (logger.isTraceEnabled()) {
			logger.trace("Rendering view [" + view + "] ");
		}
		try {
			if (mv.getStatus() != null) {
				response.setStatus(mv.getStatus().value());
			}
			// 经过下面步骤后,得到视图对象,将会调用视图接口中的render方法,决定如何响应数据出去
			view.render(mv.getModelInternal(), request, response);
		}
		catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error rendering view [" + view + "]", ex);
			}
			throw ex;
		}
	}





	// 这里是具体如何通过视图名得到一个view对象的
	@Nullable
	protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
			Locale locale, HttpServletRequest request) throws Exception {
	/**遍历视图解析器,这里有五个视图解析器分别是 ContentNegotiatingViewResolver(这个解析器进去,也是循环遍历系统中所有的解析器进行解析,相当于这个解析器就包含了其他四个解析器,具体逻辑如下) 
	**	BeanNameViewResolver   ViewResolverComposite  InternalResourceViewResolver ThymeleafViewResolver(如果没有添加Thymeleaf依赖就没有这个解析器)
	**相当于说,这段循环代码在ContentNegotiatingViewResolver处就可以解析了,轮不到其他解析器
	**/	
		if (this.viewResolvers != null) {
			// 循环遍历系统中的解析器看看那个解析器可以解析出视图对象
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					return view;
				}
			}
		}
		return null;
	}



// ContentNegotiatingViewResolver这个解析器就可以解析出view对象

@Override
	@Nullable
	public View resolveViewName(String viewName, Locale locale) throws Exception {
		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
		Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
		// 获取浏览器中能够接受的类型,和之前的内容协商很像。
		List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
		if (requestedMediaTypes != null) {
			// 获取处理后的视图集合,可能会出现多个解析器都能够通过这个视图名解析出这个视图对象 
			List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
			// 这里就是拿到最合适的那个视图对象,相当于结合浏览器进行内容协商
			View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
			if (bestView != null) {
				return bestView;
			}
		}

		String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
				" given " + requestedMediaTypes.toString() : "";

		if (this.useNotAcceptableStatusCode) {
			if (logger.isDebugEnabled()) {
				logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
			}
			return NOT_ACCEPTABLE_VIEW;
		}
		else {
			logger.debug("View remains unresolved" + mediaTypeInfo);
			return null;
		}
	}
	
	
	
	
	// ContentNegotiatingViewResolver类中的方法
	private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
			throws Exception {

		List<View> candidateViews = new ArrayList<>();
		if (this.viewResolvers != null) {
			Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
			// 再次遍历所有的视图解析器,就是这里ContentNegotiatingViewResolver解析器相当于包含了所有解析器的作用
			for (ViewResolver viewResolver : this.viewResolvers) {
				// 尝试看看那个视图解析器能够解析,每个解析器的逻辑是不同的,这里不深究。
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					candidateViews.add(view);
				}
				for (MediaType requestedMediaType : requestedMediaTypes) {
					List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
					for (String extension : extensions) {
						String viewNameWithExtension = viewName + '.' + extension;
						view = viewResolver.resolveViewName(viewNameWithExtension, locale);
						if (view != null) {
							candidateViews.add(view);
						}
					}
				}
			}
		}
		if (!CollectionUtils.isEmpty(this.defaultViews)) {
			candidateViews.addAll(this.defaultViews);
		}
		return candidateViews;
	}

上面拿到了视图对象之后,调用自定义的render进行页面渲染工作,具体如下

	// 自定义视图渲染 AbstractView类中的方法
	@Override
	public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		if (logger.isDebugEnabled()) {
			logger.debug("View " + formatViewName() +
					", model " + (model != null ? model : Collections.emptyMap()) +
					(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
		}
		// 这里就是获取接口传入进来的model对象
		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		prepareResponse(request, response);
		// 这里进行具体的渲染逻辑
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}
	
	
	
	
	// RedirectView类中的方法
	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
			HttpServletResponse response) throws IOException {

		// 获取目标路径,拼接重定向需要携带的参数等等操作
		String targetUrl = createTargetUrl(model, request);
		targetUrl = updateTargetUrl(targetUrl, model, request, response);

		// Save flash attributes
		RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);

 		// Redirect 然后进行重定向
		sendRedirect(request, response, targetUrl, this.http10Compatible);
	}
	
	
	
	
	
	// RedirectView类中的方法
		protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
			String targetUrl, boolean http10Compatible) throws IOException {

		// 重定向路径
		String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
		if (http10Compatible) {
			HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
			if (this.statusCode != null) {
				response.setStatus(this.statusCode.value());
				response.setHeader("Location", encodedURL);
			}
			else if (attributeStatusCode != null) {
				response.setStatus(attributeStatusCode.value());
				response.setHeader("Location", encodedURL);
			}
			else {
				// Send status code 302 by default.这里就是调用了最原始的HttpServletResponse重定向方法,将会重定向到一个页面
				response.sendRedirect(encodedURL);
			}
		}
		else {
			HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
			response.setStatus(statusCode.value());
			response.setHeader("Location", encodedURL);
		}
	}

 总的来说:

1、返回值以 forward: 开头的,将会是在这个对象new InternalResourceView(forwardUrl)处理,这个对象中的处理方法调用的也是request.getRequestDispatcher(path).forward(request, response),最原始的servlet方法进行转发。

2、返回值以 redirect: 开头的,将会以new RedirectView()这个对象进行处理,调用里面的reder方法,里面也是使用原始的response.sendRedirect(encodedURL)方法进行转发

3、其他类似使用了Thymeleaf模板引擎的就不细究了 

三、视图解析原理流程总结 

一、视图解析原理流程

(1)、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址

(2)、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer

(3)、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。

(4)、processDispatchResult 处理派发结果(页面改如何响应)

        1、render(mv, request, response); 进行页面渲染逻辑

                1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑

                        1、所有的视图解析器尝试是否能根据当前返回值得到View对象

                        2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()

                        3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。

                        4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作  

                                a、RedirectView 如何渲染【重定向到一个页面】

                                b、获取目标url地址

                                c、response.sendRedirect(encodedURL);