第5 章Spring Boot 的Web 开发 学习目的与要求 本章首先介绍SpringBoot的Web开发支持,然后介绍Thymeleaf视图模板引擎技术,最 后介绍SpringBoot的Web开发技术(JSON 数据交互、文件的上传与下载、异常统一处理以 及对JSP的支持)。通过本章的学习,读者应该掌握SpringBoot的Web开发技术。 本章主要内容 ● Thymeleaf模板引擎 ● 使用SpringBoot处理JSON 数据 ● SpringBoot中文件的上传与下载 ● SpringBoot的异常处理 ● SpringBoot对JSP的支持 Web开发是一种基于B/S(即浏览器/服务器)架构的应用软件开发技术,分为前端(用户 接口)和后端(业务逻辑和数据)。前端的可视化及用户交互由浏览器实现,即以浏览器作为客 户端,实现客户与服务器远程的数据交互。SpringBoot的Web开发内容主要包括内嵌Servlet 容器和SpringMVC。 5.1 SpringBoot的Web开发支持 SpringBoot提供了spring-boot-starter-web依赖模块,该依赖模块包含SpringBoot预定 义的Web开发常用依赖包,为Web开发者提供内嵌的Servlet容器(Tomcat)以及Spring MVC的依赖。如果开发者希望开发SpringBoot的Web应用程序,可以在SpringBoot项目 的pom.xml文件中添加如下依赖配置: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> SpringBoot将自动关联Web开发的相关依赖,如Tomcat、spring-webmvc等,进而对 Web开发提供支持,并实现相关技术的自动配置。 另外,开发者也可以使用IDEA 集成开发工具快速创建SpringInitializr,在NewProject 对话框中添加SpringBoot的Web依赖。 5.2 Thymeleaf模板引擎 在SpringBoot的Web应用中,建议开发者使用HTML开发动态页面。SpringBoot提 供了许多模板引擎,主要包括FreeMarker、Groovy、Thymeleaf、Velocity和Mustache。因为 Thymeleaf提供了完美的Spring MVC 支持,所以在SpringBoot的Web应用中推荐使用 Thymeleaf作为模板引擎。 第5 章 Spring Boot 的Web 开发 93 Thymeleaf是一个Java类库,是一个XML/XHTML/HTML5的模板引擎,能够处理 HTML、XML、JavaScript以及CSS,可以作为MVC Web应用的View层显示数据。 .5.2.1 Spring Boot 的Thymeleaf 支持 在SpringBoot1.X版本中,spring-boot-starter-thymeleaf依赖包含了spring-boot-starter-web 模块。但是,在Spring5 中,WebFlux 的出现对于Web 应用的解决方案不再唯一,所以 spring-boot-starter-thymeleaf依赖不再包含spring-boot-starter-web模块,需要开发者自己选 择spring-boot-starter-web模块依赖。下面通过一个实例讲解如何创建基于Thymeleaf模板 引擎的SpringBootWeb应用ch5_1。 【例5-1】 创建基于Thymeleaf模板引擎的SpringBootWeb应用ch5_1。 其具体实现步骤如下。 . 创建基于Thymeleaf模板引擎的SpringBootWeb应用ch5_1 图5.1 基于Thymeleaf模板引擎的 SpringBootWeb应用ch5_1 在IDEA 中选择File→New→Project命令,打开 NewProject对话框;在NewProject对话框中选择和输 入相关信息,然后单击Next按钮,打开新的界面;在新的 界面中选择Thymeleaf、Lombok和Spring Web依赖,单 击Create按钮,即可创建ch5_1应用。 . 打开项目目录 打开已经创建的基于Thymeleaf模板引擎的Spring BootWeb应用ch5_1,如图5.1所示。 Thymeleaf模板默认将JS脚本、CSS样式、图片等静 态文件放置在src/main/resources/static目录下,将视图 页面放在src/main/resources/templates目录下。 . 设置Web应用ch5_1的上下文路径 在ch5_1应用的application.properties文件中配置 如下内容: server.servlet.context-path=/ch5_1 . 创建控制器类 创建一个名为com.ch.ch5_1.controller的包,并在该包中创建控制器类TestThymeleafController, 具体代码如下: package com.ch.ch5_1.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class TestThymeleafController { @GetMapping("/") public String test(){ //根据Thymeleaf 模板,默认返回src/main/resources/templates/index.html return "index"; } } 94 . 新建index.html页面 在src/main/resources/templates目录下新建index.html页面,代码如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> 测试Spring Boot 的Thymeleaf 支持 </body> </html> . 运行 首先运行Ch51Application主类,然后访问“http://localhost:8080/ch5_1/”,运行结果如 图5.2所示。 图5.2 例5-1的运行结果 .5.2.2 Thymeleaf 的基础语法 . 引入Thymeleaf 首先将View层页面文件的HTML标签修改如下: <html xmlns:th="http://www.thymeleaf.org"> 然后在View层页面文件的其他标签中使用th:*动态处理页面,示例代码如下: <img th:src="'images/' + ${aBook.picture}"/> 其中,${aBook.picture}获得数据对象aBook的picture属性。 . 输出内容 使用th:text和th:utext(不对文本转义,正常输出)将文本内容输出到所在标签的body 中。如果在国际化资源文件messages_en_US.properties中有消息文本“test.myText= <strong>TestInternationalMessage</strong>”,那么在页面中可以使用如下两种方式获得 消息文本: <p th:text="#{test.myText}"></p> <!-- 对文本转义,即输出<strong>Test International Message</strong> --> <p th:utext="#{test.myText}"></p> <!-- 对文本不转义,即输出加粗的“Test International Message”--> . 基本表达式 1)变量表达式:${…} 变量表达式用于访问容器上下文环境中的变量,示例代码如下: <span th:text="${information}"> 2)选择变量表达式:*{…} 选择变量表达式计算的是选定的对象(th:object属性绑定的对象),示例代码如下: 第5 章 Spring Boot 的Web 开发 95 <div th:object="${session.user}"> name: <span th: text="*{firstName}"></span><br> <!-- firstName 为user 对象的属性--> surname: <span th: text="*{lastName}"></span><br> nationality: <span th: text="*{nationality}"></span><br> </div> 3)信息表达式:#{…} 信息表达式一般用于显示页面静态文本,将可能需要根据需求整体变动的静态文本放在 properties文件中以便维护(如国际化),通常与th:text属性一起使用。示例代码如下: <p th:text="#{test.myText}"></p> . 引入URL Thymeleaf模板通过@{…}表达式引入URL,示例代码如下: <!-- 默认访问src/main/resources/static 下的css 文件夹--> <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/> <!--访问相对路径--> <a th:href="@{/}">去看看</a> <!--访问绝对路径--> <a th:href="@{http://www.tup.tsinghua.edu.cn/index.html(param1='传参')}"> 去清华 大学出版社</a> <!-- 默认访问src/main/resources/static 下的images 文件夹--> <img th:src="'images/' + ${aBook.picture}"/> . 访问WebContext对象中的属性 Thymeleaf模板通过一些专门的表达式从模板的WebContext获取请求参数、请求、会话 和应用程序中的属性,具体如下。 ● ${xxx}:返回存储在Thymeleaf模板上下文中的变量xxx或请求request作用域中 的属性xxx。 ● ${param.xxx}:返回一个名为xxx的请求参数(可能是多个值)。 ● ${session.xxx}:返回一个名为xxx的HttpSession作用域中的属性。 ● ${application.xxx}:返回一个名为xxx的全局ServletContext上下文作用域中的 属性。 与EL表达式一样,使用${xxx}获得变量值,使用${对象变量名.属性名}获取JavaBean 属性值。需要注意的是,${}表达式只能在th标签内部有效。 . 运算符 在Thymeleaf模板的表达式中可以使用+、-、*、/、%等各种算术运算符,也可以使用>、 <、<=、>=、==、!=等各种逻辑运算符。示例代码如下: <tr th:class="(${row}== 'even')? 'even': 'odd'">…</tr> . 条件判断 1)if和unless 只有在th:if条件成立时才显示标签内容;th:unless与th:if相反,只有在条件不成立时 才显示标签内容。示例代码如下: <a href="success.html" th:if="${user != null}">成功</a> <a href="success.html" th:unless="${user = null}">成功</a> 96 2)switch语句 Thymeleaf模板也支持多路选择switch语句结构,默认属性default可用“*”表示。示例 代码如下: <div th:switch="${user.role}"> <p th:case="'admin'">User is an administrator</p> <p th:case="'teacher'">User is a teacher</p> <p th:case="*">User is a student</p> </div> . 循环 1)基本循环 Thymeleaf模板使用th:each="obj,iterStat:${objList}"标签进行迭代循环,迭代对象可 以是java.util.List、java.util.Map或数组等。示例代码如下: <!-- 循环取出集合数据--> <div class="col-md-4 col-sm-6" th:each="book:${books}"> <a href=""> <img th:src="'images/' + ${book.picture}" alt="图书封面" style="height: 180px; width: 40%;"/> </a> <div class="caption"> <h4 th:text="${book.bname}"></h4> <p th:text="${book.author}"></p> <p th:text="${book.isbn}"></p> <p th:text="${book.price}"></p> <p th:text="${book.publishing}"></p> </div> </div> 2)循环状态的使用 在th:each标签中可以使用循环状态变量,该变量有如下属性。 ● index:当前迭代对象的index(从0开始计数)。 ● count:当前迭代对象的index(从1开始计数)。 ● size:迭代对象的大小。 ● current:当前迭代变量。 ● even/odd:布尔值,当前循环是否为偶数/奇数(从0开始计数)。 ● first:布尔值,当前循环是否为第一个。 ● last:布尔值,当前循环是否为最后一个。 使用循环状态变量的示例代码如下: <!-- 循环取出集合数据--> <div class="col-md-4 col-sm-6" th:each="book,bookStat:${books}"> <a href=""> <img th:src="'images/' + ${book.picture}" alt="图书封面" style="height: 180px; width: 40%;"/> </a> <div class="caption"> <!--循环状态bookStat--> <h3 th:text="${bookStat.count}"></h3> <h4 th:text="${book.bname}"></h4> <p th:text="${book.author}"></p> 第5 章 Spring Boot 的Web 开发 97 <p th:text="${book.isbn}"></p> <p th:text="${book.price}"></p> <p th:text="${book.publishing}"></p> </div> </div> . 内置对象 在实际Web项目开发中经常传递列表、日期等数据,所以Thymeleaf模板提供了很多内 置对象,可以通过# 直接访问。这些内置对象一般都以s结尾,如dates、lists、numbers、 strings等。Thymeleaf模板通过${#…}表达式访问内置对象,常见的内置对象如下。 ● #dates:日期格式化的内置对象,操作的方法是java.util.Date类的方法。 ● #calendars:类似于#dates,但操作的方法是java.util.Calendar类的方法。 ● #numbers:数字格式化的内置对象。 ● #strings:字符串格式化的内置对象,操作的方法参照java.lang.String。 ● #objects:参照java.lang.Object。 ● #bools:判断boolean类型的内置对象。 ● #arrays:数组操作的内置对象。 ● #lists:列表操作的内置对象,参照java.util.List。 ● #sets:Set操作的内置对象,参照java.util.Set。 ● #maps:Map操作的内置对象,参照java.util.Map。 ● #aggregates:创建数组或集合的聚合的内置对象。 ● #messages:在变量表达式内部获取外部消息的内置对象。 例如,有如下控制器方法: @GetMapping("/testObject") public String testObject(Model model) { //系统时间new Date() model.addAttribute("nowTime", new Date()); //系统日历对象 model.addAttribute("nowCalendar", Calendar.getInstance()); //创建BigDecimal 对象 BigDecimal money = new BigDecimal(2019.613); model.addAttribute("myMoney", money); //字符串 String tsts = "Test strings"; model.addAttribute("str", tsts); //boolean 类型 boolean b = false; model.addAttribute("bool", b); //数组(这里不能使用int 定义数组) Integer aint[]= {1,2,3,4,5}; model.addAttribute("mya", aint); //List 列表1 List<String> nameList1 = new ArrayList<String>(); nameList1.add("陈恒1"); nameList1.add("陈恒3"); nameList1.add("陈恒2"); model.addAttribute("myList1", nameList1); //Set 集合 Set<String> st = new HashSet<String>(); st.add("set1"); 98 st.add("set2"); model.addAttribute("mySet", st); //Map 集合 Map<String, Object> map = new HashMap<String, Object>(); map.put("key1", "value1"); map.put("key2", "value2"); model.addAttribute("myMap", map); //List 列表2 List<String> nameList2 = new ArrayList<String>(); nameList2.add("陈恒6"); nameList2.add("陈恒5"); nameList2.add("陈恒4"); model.addAttribute("myList2", nameList2); return "showObject"; } 那么,可以在src/main/resources/templates/showObject.html视图页面文件中使用内置 对象操作数据。showObject.html的代码如下: <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> 格式化控制器传递过来的系统时间nowTime: <span th:text="${#dates.format(nowTime, 'yyyy/MM/dd')}"></span> <br> 创建一个日期对象: <span th:text="${#dates.create(2019,6,13)}"></span> <br> 格式化控制器传递过来的系统日历nowCalendar: <span th:text="${#calendars.format(nowCalendar, 'yyyy-MM-dd')}"></span> <br> 格式化控制器传递过来的BigDecimal 对象myMoney: <span th:text="${#numbers.formatInteger(myMoney,3)}"></span> <br> 计算控制器传递过来的字符串str 的长度: <span th:text="${#strings.length(str)}"></span> <br> 返回对象,当控制器传递过来的BigDecimal 对象myMoney 为空时返回默认值9999: <span th:text="${#objects.nullSafe(myMoney, 9999)}"></span> <br> 判断boolean 数据是否为false: <span th:text="${#bools.isFalse(bool)}"></span> <br> 判断数组mya 中是否包含元素5: <span th:text="${#arrays.contains(mya, 5)}"></span> <br> 排序列表myList1 的数据: <span th:text="${#lists.sort(myList1)}"></span> <br> 判断集合mySet 中是否包含元素set2: <span th:text="${#sets.contains(mySet, 'set2')}"></span> <br> 判断myMap 中是否包含key1 关键字: <span th:text="${#maps.containsKey(myMap, 'key1')}"></span> 第5 章 Spring Boot 的Web 开发 99 <br> 将数组mya 中的元素求和: <span th:text="${#aggregates.sum(mya)}"></span> <br> 将数组mya 中的元素求平均: <span th:text="${#aggregates.avg(mya)}"></span> <br> 如果未找到消息,则返回默认消息(如“??msgKey_zh_CN??”): <span th:text="${#messages.msg('msgKey')}"></span> </body> </html> .5.2.3 Thymeleaf 的常用属性 通过5.2.2节的学习,发现Thymeleaf语法都是通过在HTML页面的标签中添加th:xxx 关键字来实现模板套用,且其属性与HTML 页面标签基本类似。Thymeleaf的常用属性 如下。. th:action th:action用于定义后台控制器路径,类似<form>标签的action属性。示例代码如下: <form th:action="@{/login}">…</form> .th:each th:each用于集合对象的遍历,功能类似JSTL标签<c:forEach>。示例代码如下: <div class="col-md-4 col-sm-6" th:each="gtype:${gtypes}"> <div class="caption"> <p th:text="${gtype.id}"></p> <p th:text="${gtype.typename}"></p> </div> </div> .th:field th:field用于表单参数的绑定,通常与th:object一起使用。示例代码如下: <form th:action="@{/login}" th:object="${user}"> <input type="text" value="" th:field="*{username}"></input> <input type="text" value="" th:field="*{role}"></input> </form> .th:href th:href用于定义超链接,类似<a>标签的href属性。其value形式为@{/logout}。示例 代码如下: <a th:href="@{/gogo}"></a> .th:id th:id用于div的id声明,类似HTML标签中的id属性。示例代码如下: <div th:id ="stu+(${rowStat.index}+1)"></div> .th:if th:if用于条件判断,如果为否,则标签不显示。示例代码如下: <div th:if="${rowStat.index} == 0">…do something…</div> 扫一扫 视频讲解 100 .th:fragment th:fragment用于声明定义该属性的div为模板片段,常用于头文件、尾文件的引入,通常 与th:include、th:replace一起使用。 例如,在ch5_1应用的src/main/resources/templates目录下声明模板片段文件footer. html,代码如下: <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <!-- 声明片段content --> <div th:fragment="content" > 主体内容 </div> <!-- 声明片段copy --> <div th:fragment="copy" > .清华大学出版社 </div> </body> </html> 那么,可以在ch5_1应用的src/main/resources/templates/index.html文件中引入模板片 段,代码如下: <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> 测试Spring Boot 的Thymeleaf 支持<br> 引入主体内容模板片段: <div th:include="footer::content"></div> 引入版权所有模板片段: <div th:replace="footer::copy" ></div> </body> </html> .th:object th:object用于表单数据对象的绑定,将表单绑定到后台controller的一个JavaBean参 数,通常与th:field一起使用。下面通过一个实例讲解表单提交及数据绑定的实现过程。 【例5-2】 表单提交及数据绑定的实现过程。 其具体实现步骤如下。 1)创建实体类 在Web应用ch5_1的src/main/java目录下创建com.ch.ch5_1.model包,并在该包中创 建实体类LoginBean,代码如下: package com.ch.ch5_1.model; import lombok.Data; @Data 第5 章 Spring Boot 的Web 开发 101 public class LoginBean { String uname; String urole; } 2)创建控制器类 在Web应用ch5_1的com.ch.ch5_1.controller包中创建控制器类LoginController,代码 如下: package com.ch.ch5_1.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import com.ch.ch5_1.model.LoginBean; @Controller public class LoginController { @GetMapping("/toLogin") public String toLogin(Model model) { /*loginBean 与login.html 页面中的th:object="${loginBean}"相同*/ model.addAttribute("loginBean", new LoginBean()); return "login"; } @PostMapping("/login") public String greetingSubmit(@ModelAttribute LoginBean loginBean) { /*@ModelAttribute LoginBean loginBean 接收login.html 页面中的表单数据,并将 loginBean 对象保存到model 中返回给result.html 页面显示。*/ System.out.println("测试提交的数据: " + loginBean.getUname()); return "result"; } } 3)创建页面表示层 在Web应用ch5_1 的src/main/resources/templates目录下创建页面login.html和 result.html。 login.html页面的代码如下: <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <h1>Form</h1> <form action="#" th:action="@{/login}" th:object="${loginBean}" method="post"> <!--th:field="*{uname}"的uname 与实体类的属性相同,即绑定loginBean 对象--> <p>Uname: <input type="text" th:field="*{uname}" th:placeholder="请输入 用户名"/></p> <p>Urole: <input type="text" th:field="*{urole}" th:placeholder="请输入 角色"/></p> <p><input type="submit" value="Submit"/> <input type="reset" value= "Reset"/></p> </form> </body> </html>