一、写在前面
前面我们分析了Spring boot是如何解析请求参数和如何处理相应信息的 那么它是如何进行视图解析,找到我们要跳转的视图并进行视图渲染的呢?
二、写个demo
为了研究视图解析原理,我们写一个测试代码
首先是控制器类IndexController.java
package com.decade.controller;
import com.decade.pojo.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 javax.servlet.http.HttpSession;
@Controller
public class IndexController {
@GetMapping(value = {"/","/login"})
public String loginPage(){
return "login";
}
@PostMapping("/login")
public String main(User user, HttpSession session, Model model){
if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
session.setAttribute("loginUser", user);
return "redirect:/main.html";
}else {
model.addAttribute("msg","账号密码错误");
return "login";
}
}
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
Object loginUser = session.getAttribute("loginUser");
if(loginUser != null){
return "main";
}else {
model.addAttribute("msg","请重新登录");
return "login";
}
}
}
login.html放于templates文件夹下
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<meta name="description" content="">
<meta name="author" content="ThemeBucket">
<link rel="shortcut icon" href="#" type="image/png">
<title>Login</title>
<link href="css/style.css" rel="stylesheet">
<link href="css/style-responsive.css" rel="stylesheet">
</head>
<body class="login-body">
<div class="container">
<form class="form-signin" action="index.html" method="post" th:action="@{/login}">
<div class="form-signin-heading text-center">
<h1 class="sign-title">Sign In</h1>
<img src="images/login-logo.png" alt=""/>
</div>
<div class="login-wrap">
<label style="color: red" th:text="${msg}"></label>
<input type="text" name="userName" class="form-control" placeholder="User ID" autofocus>
<input type="password" name="password" class="form-control" placeholder="Password">
<button class="btn btn-lg btn-login btn-block" type="submit">
<i class="fa fa-check"></i>
</button>
<div class="registration">
Not a member yet?
<a class="" href="registration.html">
Signup
</a>
</div>
<label class="checkbox">
<input type="checkbox" value="remember-me"> Remember me
<span class="pull-right">
<a data-toggle="modal" href="#myModal"> Forgot Password?</a>
</span>
</label>
</div>
<div aria-hidden="true" aria-labelledby="myModalLabel" role="dialog" tabindex="-1" id="myModal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Forgot Password ?</h4>
</div>
<div class="modal-body">
<p>Enter your e-mail address below to reset your password.</p>
<input type="text" name="email" placeholder="Email" autocomplete="off" class="form-control placeholder-no-fix">
</div>
<div class="modal-footer">
<button data-dismiss="modal" class="btn btn-default" type="button">Cancel</button>
<button class="btn btn-primary" type="button">Submit</button>
</div>
</div>
</div>
</div>
</form>
</div>
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/modernizr.min.js"></script>
</body>
</html>
三、流程解析
我们在登录页随机输入用户名和错误的密码,然后点击登录
我们跟踪断点,他还是进入到DispatcherServlet这个类下的doDispatch()方法
然后我们走到RequestMappingHandlerAdapter类下的handleInternal()—>invokeHandlerMethod() 设置参数解析器和返回值处理器
在这里,它就会走到ServletInvocableHandlerMethod类下的invokeAndHandle()方法,通过this.invokeForRequest(webRequest, mavContainer, providedArgs) 去调用控制器类中的具体方法
我们发现,如果方法的参数是一个自定义类型对象(例如demo中的User),调用完具体方法后,我们会把他放在ModelAndViewContainer中 断点继续深入,我们走到了HandlerMethodReturnValueHandlerComposite类下的handleReturnValue(),这里它会使用this.selectHandler(returnValue, returnType) 去获取自己适合的返回值解析器
然后我们进入到ViewNameMethodReturnValueHandler这个返回值解析器
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
if (this.isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
} else if (returnValue != null) {
throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
重点:我们可以发现,目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
经过上述处理之后,我们返回到RequestMappingHandlerAdapter类中 ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest); 这行代码会将ModelAndViewContainer 中的数据放到ModelAndView中
然后返回到DispatcherServlet这个类中,处理派发结果(页面该如何响应)的关键代码this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); 我们进入该方法进行分析
this.render(mv, request, response); 进行页面渲染逻辑
String viewName = mv.getViewName(); 获取视图名称view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request); 循环遍历当前系统中所有的视图解析器尝试是否能根据当前返回值得到View对象,符合条件的视图解析器为ContentNegotiatingViewResolver(ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象),最后返回的视图对象为ThymeleafView 下面是几种不同返回值所对应的视图对象
- 返回值以 forward: 开始: new InternalResourceView(forwardUrl); —> 转发,底层为request.getRequestDispatcher(path).forward(request, response);
- 返回值以 redirect: 开始: new RedirectView() —> render底层就是重定向
- 返回值是普通字符串: new ThymeleafView()—>通过对应的解析处理,并通过writer输出流写出到页面
- 然后通过
view.render(mv.getModelInternal(), request, response); 调用视图对象自定义的render进行页面渲染工作
大家也可以输入正确的用户名和密码,debug一下重定向是如何解析的
如有错误,欢迎指正!!!
|