视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。
3.1 模板引擎-Thymeleaf
1)、thymeleaf简介
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.
Thymeleaf 是一款现代化、服务端的Java模板引擎
官网:https://www.thymeleaf.org/
https://www.thymeleaf.org/documentation.html
常见模板引擎有JSP、Velocity、Freemarker、Thymeleaf等,SpringBoot推荐的高级语言模板引擎Thymeleaf(一款现代化的Java服务端的模板引擎),语法更简单,功能更强大。
2)、基本语法
1、表达式
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#standard-expression-syntax
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | ${…} | 获取请求域、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、特殊操作
无操作: _
3)、设置属性值 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
Simple expressions:(表达式语法)
1.Variable Expressions: ${...}: 获取变量值 OGNL
1)、获取对象的属性、调用方法 OGNL expressions
2)、使用内置的基本对象 Expression Basic Objects
#ctx : the context object.当前上下文对象
#vars: the context variables.当前上下文中的变量值
#locale : the context locale.区域信息
Web的基本对象:
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.
附录Appendix A中有使用方式:例如${session.foo}从请求域中获取foo的值
3)、内置的一些工具对象 Expression Utility Objects
#execInfo : information about the template being processed.
#dates : methods for java.util.Date objects: formatting, component extraction, etc.
#calendars : analogous to #dates , but for java.util.Calendar objects.
#numbers : methods for formatting numeric objects.
#strings : methods for String objects: contains, startsWith, prepending/appending, etc.
#objects : methods for objects in general.
#bools : methods for boolean evaluation.
#arrays : methods for arrays.
#lists : methods for lists.
附录Appendix B 中有使用方式
2.Selection Variable Expressions: *{...} :选择表达式
选择表达式和${...}在功能上是一样,但是可以补充配合 th:object="${session.user}进行使用
<div th:object="${session.user}">
用th:object存储会话中user的值,此时*直接代表这个对象,直接使用*{ 对象的属性 }
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>
3.Message Expressions: #{...}:获取国际化内容
4.Link URL Expressions: @{...}:定义URL
<!-- 'http://localhost:8080/gtvg/order/details?orderId=3 ' -->
<a href="details.html" 此时 / 直接代表当前项目下,省去写主机ip和端口号及项目名
th:href="@{ /order/details( orderId=${o.id} , orderType=${o.type} ) }">view</a>
5.Fragment Expressions: ~{...}:片段引用表达式
<div th:insert="~{commons :: main}">...</div>
4)、迭代 th:each
<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>
5)、条件运算 th:if
<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>
6)、属性优先级
3.2 thymeleaf使用
1)、引入Starter启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2)、自动配置了thymeleaf
在spring-boot-autoconfigure的jar包下,提供的Thymeleaf的自动配置类
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
//Thymeleaf的自动配置类
public class ThymeleafAutoConfiguration {
}
在spring-boot-autoconfigure的jar包下,提供的thymeleaf的默认规则
//定义了Thymeleaf使用的默认规则
@ConfigurationProperties( prefix = "spring.thymeleaf" )
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";
}
自动配好的策略
- 1、所有thymeleaf的配置值都在 ThymeleafProperties
- 2、配置好了 SpringTemplateEngine
- 3、配置好了 ThymeleafViewResolver
- 4、我们只需要直接开发页面
// 页面模板放置处
public static final String DEFAULT_PREFIX = "classpath:/templates/"
// 文件的后缀名
public static final String DEFAULT_SUFFIX = ".html" //xxx.html
3)、页面开发
第一步:导入Thymeleaf的名称空间
页面中需要添加如下的名称空间,则页面中使用thymeleaf时会有提示
<html lang="en" xmlns:th="http://www.thymeleaf.org">
第二步:编写控制层
@Controller
public class ViewTestController {
@GetMapping("/hello")
public String hello(Model model){
//model中的数据会被放在请求域中
model.addAttribute("msg","大力发展工业文化");
model.addAttribute("link","http://www.baidu.com");
return "success";
}
}
第三步:使用Thymeleaf语法
页面 /templates/success.html
<!DOCTYPE html>
<!-- 导入Thymeleaf的名称空间,可以拥有提示 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- th:text 将h1标签里面的文本内容设置为指定的后端数据 -->
<!-- th:text 显示标签中的文本,原样输出,不会转义html代码,类似于jQuery中的text -->
<!-- th:utext 显示标签中的文本,会转义html代码,可以显示指定的样式,类似于jQuery中的html -->
<h1 th:text="${msg}">哈哈</h1>
<h2>
<a href="www.baidu.com" th:href="@{${link}}">去百度1</a>
<br/>
<a href="www.baidu.com" th:href="@{/link}">去百度2</a>
</h2>
</body>
</html>
#设置应用名
server:
servlet:
context-path: /app
# 这个设置后,URL要插入/app, 如http://localhost:8080/app/hello.html
3.3 案例-搭建后台管理系统
1)、项目创建
使用IDEA的Spring Initializr。勾选启动器:thymeleaf、web-starter、devtools、lombok。
2)、静态资源处理
自动配置好,我们只需要把所有静态资源放到 static 文件夹下。
/static 放置 css,js等静态资源
/templates/login.html 登录页
3)、路径构建
th:action="@{/login}"
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<form class="form-signin" method="post" th:action="@{/login}">
<!-- 消息提醒 -->
<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>
</form>
5)、页面跳转
@Controller
public class IndexController {
//跳转至登录页面
@GetMapping(value = {"/","/login"})
public String gotoLogin(){
//classpath:/templates/login.html
return "login";
}
//登录功能
@PostMapping(value = "/login")
public String login(User user, HttpSession session ,Model model){
if(StringUtils.hasLength(user.getName()) && StringUtils.hasLength(user.getEmail()) && "123@qq.com".equals(user.getEmail()) ){
session.setAttribute("user",user);
return "redirect:main";
}else{
model.addAttribute("msg","账户与邮箱不匹配!");
return "login";
}
}
//跳转至主页
@GetMapping("/main")
public String gotoMain(HttpSession session , Model model){
Object user = session.getAttribute("user");
if(user ==null){
//未登录
model.addAttribute("msg","先登录,再访问!");
return "login";
}else{
//已登录,跳转主页面main.html
return "main";
}
}
//登出
@GetMapping("/logout")
public String logout(HttpSession session , Model model){
session.invalidate();
model.addAttribute("msg","已登出!");
return "redirect:login";
}
}
6)、main.html 主页
thymeleaf内联写法 https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#inlining
显示登陆者信息
<p>Hello, [[${session.user.name}]]</p>
登出
<li><a th:href="@{/logout}"><i class="fa fa-sign-out"></i> Log Out</a></li>
7)、模型
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private String userName;
private String password;
}
3.4 案例–抽取公共页面
Template Layout https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#template-layout
<!-- 在 /templates/footer.html 中的公共部分 -->
<foot th:fragment="copy" id="data">
© 2011 The Good Thymes Virtual Grocery
</foot>
<!-- 引入的三种方式 模板引擎名 :: 片段名 -->
<body>
<!-- 插入 -->
<div th:insert="footer :: copy"></div>
<!-- 替换 -->
<div th:replace="footer :: #data"></div>
<!-- 包含 -->
<div th:include="footer :: copy"></div>
</body>
<!-- 结果 -->
<body>
<div>
<foot>
© 2011 The Good Thymes Virtual Grocery
</foot>
</div>
<foot>
© 2011 The Good Thymes Virtual Grocery
</foot>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
公共页面
/templates/common.html
<!DOCTYPE html>
<!--注意要添加xmlns:th才能添加thymeleaf的标签-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- 公共的头部 -->
<head th:fragment="commonheader">
<!--common-->
<link href="css/style.css" th:href="@{/css/style.css}" rel="stylesheet">
<link href="css/style-responsive.css" th:href="@{/css/style-responsive.css}" rel="stylesheet">
...
</head>
<body>
<!-- 公共的左侧导航栏 left side start-->
<div id="leftmenu" class="left-side sticky-left-side">
...
<div class="left-side-inner">
...
<!--sidebar nav start-->
<ul class="nav nav-pills nav-stacked custom-nav">
<li><a th:href="@{/main.html}"><i class="fa fa-home"></i> <span>Dashboard</span></a></li>
...
<li class="menu-list nav-active"><a href="#"><i class="fa fa-th-list"></i> <span>Data Tables</span></a>
<ul class="sub-menu-list">
<li><a th:href="@{/basic_table}"> Basic Table</a></li>
<li><a th:href="@{/dynamic_table}"> Advanced Table</a></li>
<li><a th:href="@{/responsive_table}"> Responsive Table</a></li>
<li><a th:href="@{/editable_table}"> Edit Table</a></li>
</ul>
</li>
...
</ul>
<!--sidebar nav end-->
</div>
</div>
<!-- left side end-->
<!-- 公共的头部菜单栏 header section start-->
<div th:fragment="headermenu" class="header-section">
<!--toggle button start-->
<a class="toggle-btn"><i class="fa fa-bars"></i></a>
<!--toggle button end-->
...
</div>
<!-- header section end-->
<!-- 公共的脚本 -->
<div id="commonscript">
<!-- Placed js at the end of the document so the pages load faster -->
<script th:src="@{/js/jquery-1.10.2.min.js}"></script>
<script th:src="@{/js/jquery-ui-1.9.2.custom.min.js}"></script>
<script th:src="@{/js/jquery-migrate-1.2.1.min.js}"></script>
<script th:src="@{/js/bootstrap.min.js}"></script>
<script th:src="@{/js/modernizr.min.js}"></script>
<script th:src="@{/js/jquery.nicescroll.js}"></script>
<!--common scripts for all pages-->
<script th:src="@{/js/scripts.js}"></script>
</div>
</body>
</html>
/templates/table/basic_table.html
<!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>Basic Table</title>
<!-- 将common.html的代码片段 包含进来-->
<div th:include="common :: commonheader"> </div>
</head>
<body class="sticky-header">
<section>
<!-- 将common.html的代码片段 替换进来-->
<div th:replace="common :: #leftmenu"></div>
<!-- main content start-->
<div class="main-content" >
<!-- 将common.html的代码片段 替换进来-->
<div th:replace="common :: headermenu"></div>
...
</div>
<!-- main content end-->
</section>
<!-- 将common.html的代码片段 包含进来-->
<!-- Placed js at the end of the document so the pages load faster -->
<div th:include="common :: #commonscript"></div>
</body>
</html>
3.5 案例-遍历数据与页面渲染
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#iteration
控制层代码
@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
//表格内容的遍历
List<User> users
= Arrays.asList(new User("zhangsan", "123456"),
new User("lisi", "123444"),
new User("haha", "aaaaa"),
new User("hehe ", "aaddd"));
model.addAttribute("users",users);
return "table/dynamic_table";
}
页面代码
<!-- 加上thymeleaf才能用 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<table class="display table table-bordered" id="hidden-table-info">
<thead>
<tr>
<th>#</th>
<th>用户名</th>
<th>密码</th>
</tr>
</thead>
<tbody>
<tr class="gradeX" th:each="user,stats:${users}">
<td th:text="${stats.count}">Trident</td>
<td th:text="${user.userName}">Internet</td>
<td >[[${user.password}]]</td>
</tr>
</tbody>
</table>
3.6 【源码分析】视图解析
原理流程
1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在ModelAndViewContainer
3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
4、processDispatchResult 处理派发结果(页面改如何响应)
render(mv, request, response); 进行页面渲染逻辑
- 根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
- 1、所有的视图解析器尝试是否能根据当前返回值得到View对象 getBeanView()
- 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
- 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。View
- 4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
- 5、renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
- RedirectView 如何渲染【重定向到一个页面】
- 1、获取目标url地址
- 2、response.sendRedirect(encodedURL);
视图解析:
返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 请求转发request.getRequestDispatcher(path).forward(request, response);
返回值以 redirect: 开始: new RedirectView() --> 响应重定向
response.sendRedirect(path);
返回值是普通字符串: new ThymeleafView()—>