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开发中的一些常用功能,敬请期待。。。