倾情力作-一文让你读懂SpringBoot2源码-web开发-页面渲染派发的全部流程

SpringBoot2核心技术-核心功能-Web开发-页面渲染派发的全部流程

本文章是把SpringBoot处理请求参数的过后,如何进行页面渲染、派发的原理 流程 例子全部细讲了一次,其中还有SpringMVC的知识,预计字数3w+,预计阅读时间1小时,虽然很长,但是流程清晰,代码基本上都有批注,读完就能完全理解页面渲染派发的全部流程和原理。


因为SpringBoot2的源码太多而且比较复杂,为的是给自己和刚入门SpringBoot的小伙伴提供一个自己学习的笔记,比较粗略还望各位多多指教,本专栏文章根据自己看书和在尚硅谷学习SpringBoot2所做下,参考了一些它们的资料所写,如有侵权会删除。
其他相关文章:
倾情力作-一文读懂SpringBoot2源码-基础入门-自动配置原理
倾情力作-一文读懂SpringBoot2源码-web开发-静态资源访问原理
倾情力作-一文让你读懂SpringBoot2源码-web开发-请求参数处理的全部流程
倾情力作-一文让你读懂SpringBoot2源码-web开发-页面渲染派发的全部流程
倾情力作-一文让你读懂SpringBoot2源码-Web开发-异常处理的全部流程

5.处理派发结果

说明:在上一篇文章倾情力作-一文让你读懂SpringBoot2源码-web开发-请求参数处理的全部流程的博客中,我们把完成了参数解析、绑定、handler对应的目标方法调用以及返回值写出到页面的操作

接下来就要开始进行最后一步处理派发结果了,处理派发结果的本质就是(数据该如何响应和渲染到页面)本次我们采用的是新的例子和Thymeleaf作为模板引擎进行举例讲解,把整个处理派发结果的流程完全走一次。

5.1视图解析与模板引擎

视图解析:每次处理完请求以后,想要跳转到某一个页面的过程。SpringBoot默认不支持 JSP(因为默认打包方式是jar包,jsp不支持在jar包内编译),需要引入第三方模板引擎技术实现页面渲染。

5.1.1视图解析

在这里插入图片描述

5.1.2模板引擎-Thymeleaf

5.1.2.1Thymeleaf简介

Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.

现代化、服务端Java模板引擎

5.1.2.2Thymeleaf基本语法

1.表达式

表达式名字语法用途
变量取值${…}获取请求域、session域、对象等值
选择变量*{…}获取上下文对象值
消息#{…}获取国际化等值
链接@{…}生成链接
片段表达式~{…}jsp:include 作用,引入公共页面片段

2.字面量

文本值: ‘one text’ , ‘Another one!’ ,…

数字: 0 , 34 , 3.0 , 12.3 ,…

布尔值: true , false

空值: null

变量:one,two,… 变量不能有空格

3.文本操作

字符串拼接: +

变量替换: |The name is ${name}|

4.数学运算

运算符: + , - , * , / , %

5.布尔运算

运算符: and , or

一元运算: ! , not

6.比较运算

比较: > , < , >= , <= ( gt , lt , ge , le **)**等式: == , != ( eq , ne )

7.条件运算

If-then: (if) ? (then)

If-then-else: (if) ? (then) : (else)

Default: (value) ?: (defaultvalue)

8.特殊操作

无操作: _

9.设置属性值-th:attr

设置单个值

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>

设置多个值

<img src="../../images/gtvglogo.png"  th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

以下两个的代替写法 th:xxxx

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">

所有h5兼容的标签写法

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes

10.迭代

<tr th:each="prod : ${prods}">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
  <td th:text="${prod.name}">Onions</td>
  <td th:text="${prod.price}">2.41</td>
  <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>

11.条件运算

<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>
5.1.2.3Thymeleaf使用

1.引入Starter

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

2.SpringBoot自动配置好了Thymeleaf

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {
    @Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingBean(name = "defaultTemplateResolver")
	static class DefaultTemplateResolverConfiguration {
		private static final Log logger = LogFactory.getLog(DefaultTemplateResolverConfiguration.class);
		private final ThymeleafProperties properties;
		private final ApplicationContext applicationContext;
		DefaultTemplateResolverConfiguration(ThymeleafProperties properties, ApplicationContext applicationContext) {
			this.properties = properties;
			this.applicationContext = applicationContext;
			checkTemplateLocationExists();
		}    
        
    @Configuration(proxyBeanMethods = false)
	protected static class ThymeleafDefaultConfiguration {
		@Bean
		@ConditionalOnMissingBean(ISpringTemplateEngine.class)
		SpringTemplateEngine templateEngine(ThymeleafProperties properties,
				ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
			SpringTemplateEngine engine = new SpringTemplateEngine();
			engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
			engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
			templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
			dialects.orderedStream().forEach(engine::addDialect);
			return engine;
		}
	}	
     @Configuration(proxyBeanMethods = false)
	static class ThymeleafViewResolverConfiguration {
		@Bean
		@ConditionalOnMissingBean(name = "thymeleafViewResolver")
		ThymeleafViewResolver thymeleafViewResolver(ThymeleafProperties properties,
					SpringTemplateEngine templateEngine) {
				ThymeleafViewResolver resolver = new ThymeleafViewResolver();
				resolver.setTemplateEngine(templateEngine);
				resolver.setCharacterEncoding(properties.getEncoding().name());
				resolver.setContentType(
						appendCharset(properties.getServlet().getContentType(), resolver.getCharacterEncoding()));
				resolver.setProducePartialOutputWhileProcessing(
						properties.getServlet().isProducePartialOutputWhileProcessing());
				resolver.setExcludedViewNames(properties.getExcludedViewNames());
				resolver.setViewNames(properties.getViewNames());
				// This resolver acts as a fallback resolver (e.g. like a
				// InternalResourceViewResolver) so it needs to have low precedence
				resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
				resolver.setCache(properties.isCache());
				return resolver;
        }      
 
 }
                                         

自动配好的策略

1、所有thymeleaf的配置值都在 ThymeleafProperties

public class ThymeleafProperties {

   private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

   public static final String DEFAULT_PREFIX = "classpath:/templates/";

   public static final String DEFAULT_SUFFIX = ".html";//xxx.html
}

2、配置好了 SpringTemplateEngine

3、配好了 ThymeleafViewResolver

4、我们只需要直接开发页面

3.页面开发初体验

@Controller
public class ViewTest {

    @GetMapping("/wxr")
    public String wxr(Model model){
        model.addAttribute("message","你好Thymeleaf!");
        model.addAttribute("link","http://www.baidu.com");
        return "success";
    }

}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
    <a href="www.atguigu.com" th:href="${link}">去百度</a>  <br/>
    <a href="www.atguigu.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>

在这里插入图片描述

5.2处理派发结果流程

5.2.1处理派发结果应用举例

controller层

package com.wxr.management.controller;

import com.wxr.management.bean.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import javax.servlet.http.HttpSession;

/**
 * 页面跳转的Controller
 *
 * @author WxrStart
 * @create 2022-04-06 14:51
 */
@Controller
public class IndexController {

    /**
     * 访问登录页
     *
     * @return
     */
    @GetMapping(value = {"/", "/login"})
    public String loginPage() {
        return "login";
    }


    /**
     * 带上了账号密码,跳转到main页面
     *
     * @param user
     * @param session
     * @return
     */
    @PostMapping("/login")
    public String main( User user, HttpSession session, Model model) {
        if (StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())) {
            //把登录成功的用户保存起来
            session.setAttribute("loginUser", user);
            //登陆成功重定向到main页面,避免刷新页面导致post表单重复提交
            return "redirect:/main.html";
        } else {
            model.addAttribute("msg", "账号密码错误");
            return "login";
        }

    }

    /**
     * 真正的去main页面,避免刷新页面导致post表单重复提交
     *
     * @return
     */
    @GetMapping("/main.html")
    public String mainPage(HttpSession session,Model model) {
        //是否登录(后面可以改为拦截器)
        Object user = session.getAttribute("loginUser");
        if(user!=null) {
            return "main";
        }else {
            //回到登录页面
            model.addAttribute("msg","请重新登录");
            return "login";
        }
    }
}

User类

@Data
public class User {
    private String userName;
    private String password;
}

static下的css、fonts、images、js,templates下的html文件的下载在这里

在这里插入图片描述

链接: https://pan.baidu.com/s/1AGjKn8EbNkj0OqXEEHqiQg 提取码: 32zf

结果示例:

在这里插入图片描述

在这里插入图片描述

5.2.2处理派发结果原理流程

使用的是5.2.1的案例

//SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()
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);

				//找到当前请求使用哪个Handler(Controller的方法)处理
                //感兴趣的可以去看我上一篇博客(请求参数处理的全部流程)的3.1.2.2
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				//找到当前请求对应的controller类型来调用相应的HandlerAdapter
                //适配器执行目标方法并确定方法参数的每一个值,感兴趣的可以去看我上一篇博客的3.2.2.1
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                
                //调用AbstractHandlerMethodAdapter.handle()
                //真正执行hanlder的方法,感兴趣的可以去看我上一篇博客(请求参数处理的全部流程)的3.2.2.2
                //在本次案例中还是先走这里,5.2.2.1
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                	if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
				//就算返回的ModelAndView为空,也会设置一个默认的ViewName
				applyDefaultViewName(processedRequest, mv);
                //并且会跳转到默认的当前请求的映射路径(比如说本次案例没有返回值,那么会跳到默认的/login页面)
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				
				dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            //处理派发结果,也就是去怎么渲染和响应去哪个页面, 5.2.2.2走这里
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
5.2.2.1真正执行handler的方法

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

略讲,细讲看3.2.2.2

//===================ha.handle(processedRequest, response, mappedHandler.getHandler())======================
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
   return handleInternal(request, response, (HandlerMethod) handler);
}

//===================return handleInternal(request, response, (HandlerMethod) handler)======================
protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
		ModelAndView mav;
		checkRequest(request);
		// Execute invokeHandlerMethod in synchronized block if required.
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					mav = invokeHandlerMethod(request, response, handlerMethod);
				}
			}
			else {
				// No HttpSession available -> no mutex necessary
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
           
            //走这里执行目标方法
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}

		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
			}
			else {
				prepareResponse(response);
			}
		}

		return mav;
	}
//================================mav = invokeHandlerMethod(request, response, handlerMethod)===================================
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
			if (this.argumentResolvers != null) {
                //设置全部的参数解析器
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {
                //设置全部的返回值解析器
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}
			invocableMethod.setDataBinderFactory(binderFactory);
			invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
			 //创建一个ModelAndViewContainer
    	  	 //为了保存的所有视图数据和模型数据
			ModelAndViewContainer mavContainer = new ModelAndViewContainer();
			mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
			mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

			AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
			asyncWebRequest.setTimeout(this.asyncRequestTimeout);

			WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
			asyncManager.setTaskExecutor(this.taskExecutor);
			asyncManager.setAsyncWebRequest(asyncWebRequest);
			asyncManager.registerCallableInterceptors(this.callableInterceptors);
			asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

			if (asyncManager.hasConcurrentResult()) {
				Object result = asyncManager.getConcurrentResult();
				mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
				asyncManager.clearConcurrentResult();
				LogFormatUtils.traceDebug(logger, traceOn -> {
					String formatted = LogFormatUtils.formatValue(result, !traceOn);
					return "Resume with async result [" + formatted + "]";
				});
				invocableMethod = invocableMethod.wrapConcurrentResult(result);
			}
			//执行目标方法,5.2.2.1.1走这里
			invocableMethod.invokeAndHandle(webRequest, mavContainer);
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}
			//获取ModelVAndView,5.2.2.1.2走这里
			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			//发出请求已完成的信号。
			webRequest.requestCompleted();
		}
	}
5.2.2.1.1执行目标方法

invocableMethod.invokeAndHandle(webRequest, mavContainer)

//======================invocableMethod.invokeAndHandle(webRequest, mavContainer)===========================
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
		//真正执行目标方法,5.2.2.1.1.1走这里
		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);

		if (returnValue == null) {
			if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
				disableContentCachingIfNecessary(webRequest);
				mavContainer.setRequestHandled(true);
				return;
			}
		}
		else if (StringUtils.hasText(getResponseStatusReason())) {
			mavContainer.setRequestHandled(true);
			return;
		}

		mavContainer.setRequestHandled(false);
		Assert.state(this.returnValueHandlers != null, "No return value handlers");
		try {
            //处理目标方法的返回值,5.2.2.1.1.2走这里
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(formatErrorForReturnValue(returnValue), ex);
			}
			throw ex;
		}
	}

5.2.2.1.1.1真正执行目标方法

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

//=============Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs)==============
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		//获取handler目标方法的所有参数值,详细过程可以见我上一篇博客的3.2.2.2.1.1.3.1.1
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
    	//走这里执行目标方法
		return doInvoke(args);
	}


//=========================================return doInvoke(args)============================================
@Nullable
	protected Object doInvoke(Object... args) throws Exception {
		Method method = getBridgedMethod();
		try {
			if (KotlinDetector.isSuspendingFunction(method)) {
				return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
			}
            //通过反射执行目标方法并且返回所需要的视图5.2.2.1.1.1.1走这里
			return method.invoke(getBean(), args);
		}
		catch (IllegalArgumentException ex) {
			assertTargetBean(method, getBean(), args);
			String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
			throw new IllegalStateException(formatInvokeError(text, args), ex);
		}
		catch (InvocationTargetException ex) {
			// Unwrap for HandlerExceptionResolvers ...
			Throwable targetException = ex.getTargetException();
			if (targetException instanceof RuntimeException) {
				throw (RuntimeException) targetException;
			}
			else if (targetException instanceof Error) {
				throw (Error) targetException;
			}
			else if (targetException instanceof Exception) {
				throw (Exception) targetException;
			}
			else {
				throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
			}
		}
	}

//=====================================执行到了目标方法,假设if语句判断成功======================================
/**
 * 带上了账号密码,跳转到main页面
 *
 * @param user
 * @param session
 * @return
 */
@PostMapping("/login")
public String main( User user, HttpSession session, Model model) {
    if (StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())) {
        //把登录成功的用户保存起来
        session.setAttribute("loginUser", user);
        //登陆成功重定向到main页面,避免刷新页面导致post表单重复提交
        return "redirect:/main.html";
    } else {
        model.addAttribute("msg", "账号密码错误");
        return "login";
    }
}
5.2.2.1.1.2处理目标方法的返回值

this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer,webRequest);

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
	//选择合适的返回值处理器,5.2.2.1.1.2.1走这里
   HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
   if (handler == null) {
 throw new IllegalArgumentException("Unknown return value type: " +returnType.getParameterType().getName());
   }
    //拿到刚刚选择的返回值处理器处理返回值,5.2.2.1.1.2.2走这里
   handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

5.2.2.1.1.2.1selectHandler(returnValue, returnType)

选择合适的返回值处理器,在这里是ViewNameMethodReturnValueHandler

private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
   boolean isAsyncValue = isAsyncReturnValue(value, returnType);
   for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
      if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
         continue;
      }
      if (handler.supportsReturnType(returnType)) {
         return handler;
      }
   }
   return null;
}

在这里插入图片描述

在这里插入图片描述

5.2.2.1.1.2.2 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);

拿到刚刚选择的返回值(ViewNameMethodReturnValueHandler)处理器处理返回值

ModelAndViewContainer-所有的数据都会放在里面,包括数据和视图地址,如果方法的参数是一个自定义类型的对象(从请求参数中确定的)就把它重新放在ModelAndViewContainer中

在这里插入图片描述

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    //判断返回值是不是字符串类型,如果是就走这里,本次案例是
   if (returnValue instanceof CharSequence) {
       //viewName="redirect:/main.html"
      String viewName = returnValue.toString();
      mavContainer.setViewName(viewName);
        //判断返回值是不是重定向,如果是就走这里,本次案例是
      if (isRedirectViewName(viewName)) {
         mavContainer.setRedirectModelScenario(true);
      }
   }
   else if (returnValue != null) {
      // should not happen
      throw new UnsupportedOperationException("Unexpected return type: " +
            returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
   }
}
//======================================isRedirectViewName(viewName)========================================
protected boolean isRedirectViewName(String viewName) {
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) || viewName.startsWith("redirect:"));
}

5.2.2.1.2获取ModelVAndView

return getModelAndView(mavContainer, modelFactory, webRequest);

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
      ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

   modelFactory.updateModel(webRequest, mavContainer);
   if (mavContainer.isRequestHandled()) {
      return null;
   }
    //获取给model中放的值,本次测试案例中没有放,所以是0
   ModelMap model = mavContainer.getModel();
    //将ModelAndViewContainer重新封装为一个ModelAndView对象
   ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
   if (!mavContainer.isViewReference()) {
      mav.setView((View) mavContainer.getView());
   }
   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;
}
5.2.2.2处理派发结果(保证页面该如何响应)

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

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

		//如果ModelAndView不为空,并且ModelAndView没有被清理过
		if (mv != null && !mv.wasCleared()) {
            //开始渲染视图,5.2.2.2.1走这里
			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);
		}
	}
5.2.2.2.1开始页面渲染

render(mv, request, response);

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		//进行一些国际化操作
Locale locale =
				(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
		response.setLocale(locale);

		View view;
   		//获取ViewName
		String viewName = mv.getViewName();
		if (viewName != null) {
			//解析ViewName,得到view对象【定义了页面的渲染逻辑】,5.2.2.2.1.1走这里
            //本次获取的View对象是RedirectView
			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) {
				request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
				response.setStatus(mv.getStatus().value());
			}
            //通过得到的View对象(本次是RedirectView),开始渲染页面,5.2.2.2.1.2走这里
			view.render(mv.getModelInternal(), request, response);
		}
		catch (Exception ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Error rendering view [" + view + "]", ex);
			}
			throw ex;
		}
	}
5.2.2.2.1.1解析ViewName,得到view对象【定义了页面的渲染逻辑】

view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

视图解析器-ViewResolver

在这里插入图片描述

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
      Locale locale, HttpServletRequest request) throws Exception {
   if (this.viewResolvers != null) {
       //遍历所有的视图解析器尝试是否能根据当前返回值得到View对象
       //本次使用的是ContentNegotiatingViewResolver(内容协商视图解析器)
      for (ViewResolver viewResolver : this.viewResolvers) {
         View view = viewResolver.resolveViewName(viewName, locale);
         if (view != null) {
            return view;
         }
      }
   }
   return null;
}
//============ContentNegotiatingViewResolver. resolveViewName(String viewName, Locale locale)==============
	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) {
            //获取候选的视图,在内容协商视图解析器中获取到可以适配的视图5.2.2.2.1.1.1走这里
			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;
		}
	}

5.2.2.2.1.1.1获取候选的视图,在内容协商视图解析器中获取到可以适配的视图

getCandidateViews(viewName, locale, requestedMediaTypes)

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

在这里插入图片描述

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的视图解析器尝试是否能根据当前返回值得到View对象
			for (ViewResolver viewResolver : this.viewResolvers) {
                //在这里使用的是ThymeleafViewResovler的视图解析器,5.2.2.2.1.1.1.1走这里
				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;
	}

//====================ThymeleafViewResovler.resolveViewName(viewName, locale)===============================
public View resolveViewName(String viewName, Locale locale) throws Exception {
    	//如果没有启用缓存,这次走这里
		if (!isCache()) {
            //创建了一个View,5.2.2.2.1.1.1.1.1走这里
			return createView(viewName, locale);
		}
		else {
			Object cacheKey = getCacheKey(viewName, locale);
			View view = this.viewAccessCache.get(cacheKey);
			if (view == null) {
				synchronized (this.viewCreationCache) {
					view = this.viewCreationCache.get(cacheKey);
					if (view == null) {
						// Ask the subclass to create the View object.
						view = createView(viewName, locale);
						if (view == null && this.cacheUnresolved) {
							view = UNRESOLVED_VIEW;
						}
						if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
							this.viewAccessCache.put(cacheKey, view);
							this.viewCreationCache.put(cacheKey, view);
						}
					}
				}
			}
			else {
				if (logger.isTraceEnabled()) {
					logger.trace(formatKey(cacheKey) + "served from cache");
				}
			}
			return (view != UNRESOLVED_VIEW ? view : null);
		}
	}
//============================ThymeleafViewResovler.createView(viewName, locale)============================
/**
  返回值以 forward: 开始: new InternalResourceView(forwardUrl); -->  转发request.getRequestDispatcher(path).forward(request, response);
  返回值以redirect: 开始:new RedirectView() --》 render就是重定向
  返回值是普通字符串: new ThymeleafView()--->


*/

@Override
    protected View createView(final String viewName, final Locale locale) throws Exception {
        //多余代码省略
    
        //判断视图名是否是"redirect:"开头的
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            vrlogger.trace("[THYMELEAF] View \"{}\" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName);
            //截取viewName的名字"redirect:/main.html"--->"/main.html"
            final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length(), viewName.length());
            //新建了一个RedirectView对象返回出去
            final RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
            return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, REDIRECT_URL_PREFIX);
        }
      
        //判断视图名是否是"forward:"开头的
        if (viewName.startsWith(FORWARD_URL_PREFIX)) {
            // The "forward:" prefix will actually create a Servlet/JSP view, and that's precisely its aim per the Spring
            // documentation. See http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-redirecting-forward-prefix
            vrlogger.trace("[THYMELEAF] View \"{}\" is a forward, and will not be handled directly by ThymeleafViewResolver.", viewName);
            final String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length(), viewName.length());
            return new InternalResourceView(forwardUrl);
        }
        
        
        
        //多余代码省略
    
    
5.2.2.2.1.2通过得到的View对象,开始渲染页面

这次获取到的是View对象是RedirectView

view.render(mv.getModelInternal(), request, response);

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

   Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
   prepareResponse(request, response);
    //将模型转换为请求参数并重定向到给定的 URL
   renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
//===========renderMergedOutputModel(mergedModel, getRequestToExpose(request), response)===================
	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		//1.获取目标的URL地址("/main.html")
		String targetUrl = createTargetUrl(model, request);
		targetUrl = updateTargetUrl(targetUrl, model, request, response);

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

		//2.开始重定向
		sendRedirect(request, response, targetUrl, this.http10Compatible);
	}
//========================================1.createTargetUrl(model, request)=================================
	protected final String createTargetUrl(Map<String, Object> model, HttpServletRequest request)
			throws UnsupportedEncodingException {

		StringBuilder targetUrl = new StringBuilder();
        //获取到url地址,在之前已经放进去了
		String url = getUrl();
		Assert.state(url != null, "'url' not set");

		if (this.contextRelative && getUrl().startsWith("/")) {
			// Do not apply context path to relative URLs.
			targetUrl.append(getContextPath(request));
		}
		targetUrl.append(getUrl());

		String enc = this.encodingScheme;
		if (enc == null) {
            //设置编码格式
			enc = request.getCharacterEncoding();
		}
		if (enc == null) {
			enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
		}

		if (this.expandUriTemplateVariables && StringUtils.hasText(targetUrl)) {
			Map<String, String> variables = getCurrentRequestUriVariables(request);
			targetUrl = replaceUriTemplateVariables(targetUrl.toString(), model, variables, enc);
		}
		if (isPropagateQueryProperties()) {
			appendCurrentQueryParams(targetUrl, request);
		}
		if (this.exposeModelAttributes) {
            //如果有属性需要重定向那么就把属性拼接到查询的URL后面
			appendQueryProperties(targetUrl, model, enc);
		}

		return targetUrl.toString();
	}
//====================sendRedirect(request, response, targetUrl, this.http10Compatible)=====================
	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 {
				//调用原生servlet请求的重定向方法,完成页面的跳转
				response.sendRedirect(encodedURL);
			}
		}
		else {
			HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
			response.setStatus(statusCode.value());
			response.setHeader("Location", encodedURL);
		}
	}

自此最后一步处理派发结果已经完成,我们通过两篇超10w字的长文,深刻理解了SpringBoot在一个请求从客户端发送给服务器后,服务器是符合完成了参数解析、绑定、handler对应的目标方法调用、返回值写出到页面的操作以及处理派发结果 (渲染或者是跳转页面),后续的文章我们会讲一些SpringBoot在web开发中的一些常用功能,敬请期待。。。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值