4.3 SpringBoot Web开发-视图解析与模板引擎

视图解析: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/

Thymeleaf官方文档

https://www.thymeleaf.org/documentation.html

常见模板引擎有JSP、Velocity、Freemarker、Thymeleaf等,SpringBoot推荐的高级语言模板引擎Thymeleaf(一款现代化的Java服务端的模板引擎),语法更简单,功能更强大。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zkAgW9b9-1641993728247)(image/image-20210330100319602.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tqqOUbfj-1641993728249)(image/image-20210330100338657.png)]

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)、属性优先级

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b31SoH4U-1641993728250)(image/image-20210330101129143.png)]

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">
  &copy; 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>
      &copy; 2011 The Good Thymes Virtual Grocery
    </foot>
  </div>

  <foot>
    &copy; 2011 The Good Thymes Virtual Grocery
  </foot>

  <div>
    &copy; 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()—>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L1evSkA9-1641993728253)(image/19.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uGkymV1M-1641993728253)(image/20.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qLc1z5yG-1641993728255)(image/21.jpg)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

what's your name.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值