什么是视图解析
简单来说视图解析就是处理视图的一种手段
处理手段就是处理视图的方式
所以为了我们的数据能渲染到页面是上,我们需要视图解析
怎么样进行视图解析
我们需要模板引擎来进行视图解析,但是springBoot不支持jsp模板引擎,所以在这边我们使用thymelea
thymelea是一个现代化的服务端的java模板引擎,对于后端开发人员,语法比较简单
如何使用thymelea
方法:非常的简单,以下面这个例子
th:text就是把我们后台获取到的msg.headers.name,渲染到这个标签上,如果成功获取了,就渲染,没获取成功就用原来的
基础的使用语法
使用thymelea进行测试(在springboot01项目进行测试)
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.底层是如何进行 thymelea自动配置的
放入模板引擎解析器
什么有前缀有后缀的
但是这些也都是从配置文件中进行绑定的
还放入了模板引擎
还放入了视图解析器
我们看到上面很多都使用了preperties,这个东西其实就是配置文件,是已经被写死的
里面也规定了页面放到哪里,也规定了使用的后缀为.html,所以的页面都会跳转到xxx.html
3.开始测试
按照配置文件的要求,建立文件夹
按照官方模板,引入thymeleaf的名称空间
现在我们写th就有提示了
前端从路径发起/LALALA请求,后端使用这个方法进行处理,并且传入一个model对象,model对象在请求域中放入值,最后跳转到success页面
注意:为什么我们不写全success.html,因为在源码里会自动给我们添加.html后缀
然后就会去templates目录下找success.html页面
@Controller
public class ViewTestController {
@GetMapping("/LALALA")
public String LALALA(Model model){
model.addAttribute("msg","你好:LALALA");
return "success";
}
}
现在我们只要在标签中获取请求域中名称为msg的值就会自动把hhhhhhh渲染覆盖,但是如果单独拿出来这个前端页面,显示的只是hhhhhh,只有在服务器上运行才可以
<body>
<h1 th:text="${msg}">hhhhhhhh</h1>
</body><body>
现在放文本文件时th:text,我们也可以放入超链接
不同的是之前我们是修改文本的值,现在要修改a标签中href属性的值
但是之前不是说超连接要放在@{}里面吗?
我们添加一个试试
我们发现第一个可以,第二个不行了
查看网页发现
我们第一个真正的解析了link的值,第二个只是把link当成了一个字符串地址去访问
也就是说第二个使用超链接的意思是想访问我们的项目路径
而且它还会根据需求自动拼装,如果我们给项目加了统一的请求路径
它也自动的给我们加了/world
使用thymeleaf快速构建后台管理系统
新建一个项目
把前端需要的静态资源放入static文件夹中
把登录页面放入templates文件夹中
我们项目一旦进来(访问根路径)肯定需要展示的就是登陆页面,要写controller喽
注意包的路径
@Controller
//登录的controller
public class IndexController {
//来登录页
//处理请求,路径为/或者/login都可以跳转到login.html
@GetMapping(value = {"/","/login"})
public String loginPage(){
//跳转到login.html
return "login";
}
}
现在可以成功的进入登录页面了
现在我们的地址是这个/login,只要我们在这个/login用表单提交了账号和密码,我们就可以到首页(index.html)
所以我们带着表单提交应该是post请求,而且表单的acction应该是/login
引入名称空间
<form class="form-signin" action="http://view.jqueryfuns.com/2014/4/10/7_df25ceea231ba5f44f0fc060c943cdae/index.html" method="post" th:action="@{/login}">
<div class="form-signin-heading text-center">
<h1 class="sign-title">登录</h1>
<img src="images/login-logo.png" alt=""/>
</div>
此时就会以post的方式拼接路径/login来发送请求,因为我们前面的进入登录页面也有/login,但是那个方式是get,现在我们使用post进行区分和提交表单
//从登录页面提交账号密码到主页
@PostMapping("/login")
public String main(String username,String password){
return "index";
}
此时就可以进入主界面了
但是现在有一个问题,我们在主界面的每一次刷新现在相当于都在对表单进行重复提交,有没有解决方法?
有的,我们现在只是经过了一次跳转,我们可以把登录成功之后设置为重定向,自己再定义一个get请求用于接收,这样子就不会造成表单的重复提交
中间通过main.html这个中转重定向把post请求转换为了get请求
//从登录页面提交账号密码到主页
@PostMapping("/login")
public String main(String username,String password){
//登录成功重定向到main页面
return "redirect:/main.html";
}
//解决表单重复提交,现在是真正的要跳转到main页面
@GetMapping("/main.html")
public String mainPage(){
return "main";
}
现在我们的表单只会提交一次了
现在我们如果一直刷新,刷新的只是这个请求,解决了重复提交
但是现在又出现一个问题,现在所有的浏览器只要访问我们的地址就可以访问到我们的主界面,但是我们想只有登录之后才可以访问到我们的主界面,有没有办法解决?
我们先模拟好用户对象,里面有账号名称和密码
@Data
public class user {
private String userName;
private String password;
}
在外面前端的表单里也填写好对应的名称
<input type="text" name="userName" class="form-control" placeholder="用户名" autofocus>
<input type="password" name="password" class="form-control" placeholder="密码">
后端接收post请求时,直接传入user对象,在方法中进行判断(现在简单的判断方法就是账号和密码(密码要为123546)有内容就可以登录了)
传入session,我们可以把可以登录的用户存储到session里面
我们也可以传入一个model,用于如果账号和密码都没有填写,我们再让它转发到登录页面(使用get请求的login)并且提示(放入model),账号密码错误
现在只有输入正确的账号密码才能进入主界面了
但是怎么防止别人输入路径就能访问主界面呢?
我们之前把可以登录的用户放到session里面了
我们在真正的去main页面的方法里对于session里面有没有用户进行判断就可以了
//解决表单重复提交,现在是真正的要跳转到main页面
@GetMapping("/main.html")
public String mainPage(HttpSession session){
Object loginUser = session.getAttribute("loginUser");
if (loginUser!=null){
//如果可登录的用户不为空,说明此时可以登录
return "main";
}else {
//否则还是回到初始的登录页面
//跳转到login.html
return "login";
}
}
并且传入一个model信息给用户提示当前没有登录
//解决表单重复提交,现在是真正的要跳转到main页面
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
Object loginUser = session.getAttribute("loginUser");
if (loginUser!=null){
//如果可登录的用户不为空,说明此时可以登录
return "main";
}else {
//给用户提示
model.addAttribute("msg","请重新登录");
//否则还是回到初始的登录页面
//跳转到login.html
return "login";
}
}
实现给用户提示(我们的提示都放到model的msg里面了)
在前端表单上面添加内容
<label style="color: red" th:text="${msg}"></label>
<input type="text" name="userName" class="form-control" placeholder="用户名" autofocus>
<input type="password" name="password" class="form-control" placeholder="密码">
测试:
![](https://i-blog.csdnimg.cn/blog_migrate/c404be0ed6d4351b65b40892f7651641.png)
直接访问路径(需要更换浏览器或者先清理缓存)
现在登录的问题大部分解决了,但是我们想登录之后在右上角实时显示登录的账号,可以吗?
可以,先找到前端所在的位置
找到对应的代码
但是我们之前的thymeleaf都是在标签里写的,这玩意没有标签
可以使用thymeleaf的行内写法
我们之前把登录成功的人的登录信息都存放到loginuser里面了,所以现在直接拿出来用就可以了
我们已经完成了登录的基本流程,但是我们如果想点击其他的页面,还是会404,想办法把其他页面搞进来(以DataTables为例子)
把我们的静态页面放进来(我们的后台管理系统很大,所以我们可以新建一个文件夹,专门放table的文件)
并且给这几个新加的页面都添上thymeleaf名称空间
书写table的controller
@Controller
public class TableController {
@GetMapping("/basic_table")
public String basic_table(){
return "tables/basic_table";
}
@GetMapping("/dynamic_table")
public String dynamic_table(){
return "tables/dynamic_table";
}
@GetMapping("/responsive_table")
public String responsive_table(){
return "tables/responsive_table";
}
@GetMapping("/editable_table")
public String editable_table(){
return "tables/editable_table";
}
}
注意:为什么现在我们的return跳转需要加/tables呢?
因为我们之前源码里写了,只会找以templates目录下的,以.html结尾的文件,现在我们多了tables目录,所以需要自己加
我们现在的前端文件,超链接标签里的href一定也要改成和我们请求相互对应的(不能加.html)
例如
现在都可以进来了
但是现在有一个问题,我们如果进入了一个table,我们在这里面切换其他的table会404
这是因为我们table里面的html里面的a标签的herf还是给我们加了.html,我们把这四个table里面的.html去掉就可以了
但是我们在去掉的时候发现一个问题,我们现在的四个table,他们的左边和上面部分其实都是一样的,我们可不可以把公共的这一部分提取出来?
首先观察重合的部分
1.引入的js文件
2.引入的部分css文件
3..左侧导航栏部分
4.头部内容信息
我们写一个公共的页面进行抽取
抽取公共的js
抽取公共的css
抽取公共的左侧导航栏和头部内容
现在我们已经对公共内容进行了抽取,如何应用?
首先加入thymeleaf名称空间
把我们的链接都使用thymeleaf包装起来
如何使用正确的语法进行抽取
下面这个例子我们的footer就是我们的公共内容
把公共的部分使用th:fragement=自己起的名字进行标注
使用的时候使用th:insert=~{公共内容页面的名字::自己起的名字}进行引用
或者直接th:insert=公共内容页面的名字::自己起的名字也可以
或者我们可以使用选择器
自己设置一个id
使用的时候对应好自己的id,但是多一个#
现在对于我们的公共内容进行声明
我们的使用方式也有好几种
insert会把我们的内容放在我们的div中
replace会把我们写的div给替换掉
include会把我们公共内容外面的标签给去掉,直接把内容放在div中
现在就可以真正的进行替换了
对于main
CSS
JS
注意现在是选择器
左菜单
头部分内容
现在的main.html是成功的
那么我们对于其他四个页面全部都修改了
经过所有的替换,都是正常的
几个小bug
我们希望点击右上角的DashBoard,就跳转回主页
找到这个对于的超链接标签修改就可以了
我们现在的页面右上角又是不会动的了
说明我们的公共页面没有成功引入
现在可以了
我们还想点击退出就退到登录页
可以了
现在我们完成了一些想要的功能,但是如果我们想遍历的把我们想加的数据加入先进的表如何做?
先制作一些假数据
把现在的假数据放入请求域中
@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
//表格内容的遍历
List<User> users = Arrays.asList(new User("adasdad", "458613"),
new User("adS", "458613"),
new User("kuj", "458613"),
new User("adastrsyhdad", "458613"),
new User("vbcxcvb", "458613"));
model.addAttribute("users",users);
return "tables/dynamic_table";
}
找到前端的表单,把请求域中的东西进行遍历
修改表头
指明在请求域中遍历的对象,并且每次拿出来一个就放在一个user对象中
获取用户名和密码
现在测试
除了我们想要的自动id没显示,其他的正常
我们可以加一个遍历的状态(看成对象),这个状态有自动递增id的方法
成功了
视图解析的源码分析(以登录方法开始测试)
首先所有的请求都是从dispatchservlet的dodispatch方法开始
找哪个handler(目标方法)能够处理当前的请求
找handler的适配器(一个大的反射工具)
使用适配器真正的执行方法
进入适配器
执行目标方法
设置参数解析器和返回值处理器
执行真正的目标方法
成功登录,重定向到/main.html
执行完目标方法,跳转接口
得到了我们的返回值,字符串的重定向操作
使用返回值处理器进行处理
跳转接口,使用返回值处理器,看看哪个处理器能够解析现在的结果
使用for循环寻找返回值处理器
最终给我们选择的handler是
为什么可以选择它?
因为它的判断条件是现在返回为空或者返回字符串(符合条件)
回到
使用我们选择的处理器进行处理
进入
如果返回值是字符串,就把字符串封装到viewName
把返回值放入mavContanier
目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
判断是不是需要重定向
它如何判断?判断我们的视图名是不是以重定向开头,我们是的
这边的主要目的就是把我们的字符串重定向结果装起来
转换接口,向上返回(到了真正执行完目标方法的地方)
现在我们的mavContranier
为什么里面会有一个user?
因为我们传入的是一个user对象,也会被放到这个里面
方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
继续向下执行
因为我们参数传入了一个model,所以会把我们model里面存放的值也放起来(如果登录失败提醒用户),但是现在我们登录成功,这里面没有值
把我们的视图名和mavcon封装起来变成mav
最后返回
方法执行结束后不用跳转接口,还是继续向下执行(会到真正的执行完方法并且封存好返回值mav之后)
任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
当前方法执行结束后,再次跳转接口(回到真正执行完handle之后并且拿到封装好的视图和数据的mv)
看看当前的mv是不是空,如果是空就跳转回原来的页面
处理派发结果
开始处理派发结果(不用跳转接口)
processDispatchResult 处理派发结果(页面改如何响应)
先判断处理期间有没有失败
看看mv是不是空或者有没有被处理过(我们现在没有)
使用rep和resp和mv进行渲染
render(mv, request, response); 进行页面渲染逻辑
不用跳转接口,还是在这个接口中
从mv中拿到视图名
使用视图解析器进行解析最后得到View对象
什么是view对象?
是一个接口
规定了一个render方法,定义了页面的渲染逻辑
根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
不用跳转接口,现在想得到view对象
先判断视图解析器是不是空
视图解析器的内容是
for循环所有的视图解析器尝试是否能根据当前返回值得到View对象
跳转接口,进入第一次循环,看看ContentNegotiating视图解析器可不可以
得到请求所有能接收的数据类型
获取候选的视图
如何获取候选的视图?不用跳转接口
其实在这里面也是for循环所有的视图解析器,一个一个寻找(自己除外)
直到找到了thymeleaf视图解析器
thymeleaf视图解析器如何判断?跳转接口
thymeleaf视图解析器看看自己能不能创建一个View类型的对象
跳转接口进行创建尝试
先看看视图名称是不是以重定向开头(是的)
对视图名称截取字符串,只保留/main.html
截取之后进行new创建,真正的创建了View对象
ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
最后向上返回
向上返回,返回到已经获取到了候选的视图
现在的candidateView里面就是我们筛选出来候选的视图
最终决定返回哪个页面,封装成View对象
得到了 redirect:/main.html --> Thymeleaf new RedirectView()
最后向上返回我们需要的view对象
视图对象调用自己的渲染方法
那么View是怎么渲染的呢?跳转接口
跳转接口
获取到了想去页面的url地址
最后使用url调用最原生的重定向方法
最终完成了整个的重定向
视图解析的源码分析(以向先进表中放数据方法开始测试)
之前的流程一样,直到使用for循环到ContenNegotiating视图解析器看看能不能进行视图解析
使用for循环看看哪个视图解析器能创建view对象![](https://i-blog.csdnimg.cn/blog_migrate/d8df848d5ce2ee93ca40870107960e94.png)
来到thymeleaf的视图解析器
看看是不是以重定向为开头(我们不是)
看看是不是以转发为开头(我们不是)
如果都不是就对页面进行加载
不用跳转接口
先拿到IOC容器工厂
在容器中找视图的名称,看看存不存在(现在不存在)
如果不存在会准备一个视图对象
创建view对象
从容器中得到一个thymeleafView
设置好模板的名称
还要设置一堆东西,最后向上返回
再次向上返回,使用thymeleafView进行渲染
使用thymeleafView进行渲染
thymeleaf自己拿到模板引擎
先拿到我们想给页面渲染的数据
拿到我们想要响应的类型和编码
利用模板引擎调用方法
使用模板引擎,转换接口
使用模板引擎直接把内容进行写出
把页面内容进行刷出
视图解析原理总结