第5章Spring Boot的Web开发 学习目的与要求 本章首先介绍Spring Boot的Web开发支持,然后介绍Thymeleaf视图模板引擎技术,最后介绍Spring Boot的Web开发技术(JSON数据交互、文件上传与下载、异常统一处理以及对JSP的支持)。通过本章的学习,掌握Spring Boot的Web开发技术。 本章主要内容  Thymeleaf模板引擎。  Spring Boot处理JSON数据。  Spring Boot的文件上传与下载。  Spring Boot的异常处理。 Web开发是一种基于B/S架构(即浏览器/服务器)的应用软件开发技术,分为前端(用户接口)和后端(业务逻辑和数据),前端的可视化及用户交互由浏览器实现,即以浏览器作为客户端,实现客户与服务器远程的数据交互。Spring Boot的Web开发内容主要包括内嵌Servlet容器和Spring MVC。 5.1Spring Boot的Web开发支持 Spring Boot提供了springbootstarterweb依赖模块,该依赖模块包含Spring Boot预定义的Web开发常用依赖包,为Web开发者提供了内嵌的Servlet容器(Tomcat)以及Spring MVC的依赖。如果开发者希望开发Spring Boot的Web应用程序,可以在Spring Boot项目的pom.xml文件中添加如下依赖配置: 视频讲解 org.springframework.boot spring-boot-starter-web Spring Boot将自动关联Web开发的相关依赖,如tomcat、springwebmvc等,进而对Web开发提供支持,并对相关技术的配置实现自动配置。 另外,开发者也可以使用Spring Tool Suite集成开发工具快速创建Spring Starter Project,在New Spring Starter Project Dependencies对话框中添加Spring Boot的Web依赖,如图5.1所示。 图5.1添加Spring Boot的Web依赖 视频讲解 5.2Thymeleaf模板引擎 在Spring Boot的Web应用中,建议开发者使用HTML完成动态页面。Spring Boot提供了许多模板引擎,主要包括FreeMarker、Groovy、Thymeleaf、Velocity和Mustache。因为Thymeleaf提供了完美的Spring MVC支持,所以在Spring Boot的Web应用中推荐使用Thymeleaf作为模板引擎。 Thymeleaf是一个Java类库,是一个XML/XHTML/HTML5的模板引擎,能够处理HTML、XML、JavaScript以及CSS,可以作为MVC Web应用的View层显示数据。 5.2.1Spring Boot的Thymeleaf支持 在Spring Boot 1.X版本中,springbootstarterthymeleaf依赖包含了springbootstarterweb模块。但是,在Spring 5中,WebFlux的出现对于Web应用的解决方案将不再唯一。所以,springbootstarterthymeleaf依赖不再包含springbootstarterweb模块,需要开发人员自己选择springbootstarterweb模块依赖。下面通过一个实例,讲解如何创建基于Thymeleaf模板引擎的Spring Boot Web应用ch5_1。 【例51】创建基于Thymeleaf模板引擎的Spring Boot Web应用ch5_1。 具体实现步骤如下。 1. 创建Spring Starter Project 选择菜单File|New|Spring Starter Project,打开New Spring Starter Project对话框,在该对话框中选择和输入相关信息,如图5.2所示。 图5.2创建基于Thymeleaf模板引擎的Spring Boot Web应用ch5_1 2. 选择依赖 单击图5.2中的Next按钮,打开New Spring Starter Project Dependencies对话框,选择Spring Web Starter和Thymeleaf依赖,如图5.3所示。 3. 打开项目目录 单击图5.3中的Finish按钮,创建如图5.4所示的基于Thymeleaf模板引擎的Spring Boot Web应用ch5_1。 图5.3选择Spring Web Starter和Thymeleaf依赖 图5.4基于Thymeleaf模板引擎的Spring Boot Web应用ch5_1 Tymeleaf模板默认将JS脚本、CSS样式、图片等静态文件放置在src/main/resources/static目录下,将视图页面放在src/main/resources/templates目录下。 4. 创建控制器类 创建一个名为com.ch.ch5_1.controller的包。并在该包中创建控制器类TestThymeleafController,代码如下: package com.ch.ch5_1.controller; import org.springframework.stereotype.Controller; @Controller public class TestThymeleafController { @RequestMapping("/") public String test(){ //根据Tymeleaf模板,默认将返回src/main/resources/templates/index.html return "index"; } } 5. 新建index.html页面 在src/main/resources/templates目录下新建index.html页面,代码如下: Insert title here 测试Spring Boot的Thymeleaf支持 图5.5例51运行结果 6. 运行测试 首先,运行Ch51Application主类。然后,访问http://localhost:8080/ch5_1/(因为配置文件中配置了Web应用的上下文路径为ch5_1)。运行效果如图5.5所示。 5.2.2Thymeleaf基础语法 1. 引入Thymeleaf 首先,将View层页面文件的html标签修改如下: 然后,在View层页面文件的其他标签里,使用th:*动态处理页面,示例代码如下: 其中,${aBook.picture}获得数据对象aBook的picture属性。 2. 输出内容 使用th:text和th:utext(对HTML标签解析)将文本内容输出到所在标签的body中。假如在国际化资源文件messages_en_US.properties中有消息文本“test.myText=Test International Message”,那么在页面中可以使用如下两种方式获得消息文本:

3. 基本表达式 1) 变量表达式: ${…} 变量表达式用于访问容器上下文环境中的变量,示例代码如下: 2) 选择变量表达式: *{…} 选择变量表达式计算的是选定的对象(th:object属性绑定的对象),示例代码如下:
name:
surname:
nationality:
3) 信息表达式: #{…} 信息表达式一般用于显示页面静态文本,将可能需要根据需求而整体变动的静态文本放在properties文件中以便维护(如国际化),通常与th:text属性一起使用,示例代码如下:

4. 引入URL Thymeleaf模板通过@{…}表达式引入URL,示例代码如下: 去看看 去清华大学出版社 5. 访问WebContext对象中的属性 Thymeleaf模板通过一些专门的表达式从模板的WebContext获取请求参数、请求、会话和应用程序中的属性,具体如下: ${xxx}将返回存储在Thymeleaf模板上下文中的变量xxx或请求request作用域中的属性xxx。 ${param.xxx}将返回一个名为xxx的请求参数(可能是多个值)。 ${session.xxx}将返回一个名为xxx的HttpSession作用域中的属性。 ${application.xxx}将返回一个名为xxx的全局ServletContext上下文作用中的属性。 与EL表达式一样,使用${xxx}获得变量值,使用${对象变量名.属性名}获取JavaBean属性值。但需要注意的是,${}表达式只能在th标签内部有效。 6. 运算符 在Thymeleaf模板的表达式中可以使用+、-、*、/、%等各种算术运算符,也可以使用>、<、<=、>=、==、!=等各种逻辑运算符。示例代码如下: … 7. 条件判断 1) if和unless 只有在th:if条件成立时才显示标签内容; th:unless与th:if相反,只有在条件不成立时才显示标签内容。示例代码如下: 成功 成功 2) switch语句 Thymeleaf模板也支持多路选择switch语句结构,默认属性default可用“*”表示。示例代码如下:

User is an administrator

User is a teacher

User is a student

8. 循环 1) 基本循环 Thymeleaf模板使用th:each="obj,iterStat:${objList}"标签进行迭代循环,迭代对象可以是java.util.List、java.util.Map或数组等。示例代码如下:
图书封面

2) 循环状态的使用 在th:each标签中可以使用循环状态变量,该变量有如下属性。 index: 当前迭代对象的index(从0开始计数)。 count: 当前迭代对象的index(从1开始计数)。 size: 迭代对象的大小。 current: 当前迭代变量。 even/odd: 布尔值,当前循环是否是偶数/奇数(从0开始计数)。 first: 布尔值,当前循环是否是第一个。 last: 布尔值,当前循环是否是最后一个。 使用循环状态变量的示例代码如下:
图书封面

9. 内置对象 在实际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: 在变量表达式内部获取外部消息的内置对象。 假如有如下控制器方法: @RequestMapping("/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 nameList1 = new ArrayList(); nameList1.add("陈恒1"); nameList1.add("陈恒3"); nameList1.add("陈恒2"); model.addAttribute("myList1", nameList1); //Set集合 Set st = new HashSet(); st.add("set1"); st.add("set2"); model.addAttribute("mySet", st); //Map集合 Map map = new HashMap(); map.put("key1", "value1"); map.put("key2", "value2"); model.addAttribute("myMap", map); //List列表2 List nameList2 = new ArrayList(); nameList2.add("陈恒6"); nameList2.add("陈恒5"); nameList2.add("陈恒4"); model.addAttribute("myList2", nameList2); return "showObject"; } 那么,可以在src/main/resources/templates/showObject.html视图页面文件中使用内置对象操作数据。showObject.html的代码如下: Insert title here 格式化控制器传递过来的系统时间nowTime
创建一个日期对象
格式化控制器传递过来的系统日历nowCalendar:
格式化控制器传递过来的BigDecimal对象myMoney:
计算控制器传递过来的字符串str的长度:
返回对象,当控制器传递过来的BigDecimal对象myMoney为空时,返回默认值9999:
判断boolean数据是否是false:
判断数组mya中是否包含元素5:
排序列表myList1的数据:
判断集合mySet中是否包含元素set2:
判断myMap中是否包含key1关键字:
将数组mya中的元素求和:
将数组mya中的元素求平均:
如果未找到消息,则返回默认消息(如"??msgKey_zh_CN??"): 5.2.3Thymeleaf的常用属性 通过5.2.2节的学习,我们发现Thymeleaf语法的使用都是通过在html页面的标签中添加th:xxx关键字来实现模板套用,且其属性与html页面标签基本类似。常用属性有以下几种。 1. th:action th:action定义后台控制器路径,类似于
标签的action属性。示例代码如下: …
2. th:each th:each用于集合对象遍历,功能类似于JSTL标签。示例代码如下:

3. th:field th:field常用于表单参数绑定,通常与th:object一起使用。示例代码如下:
4. th:href th:href用于定义超链接,类似于标签的href属性。value形式为@{/logout},示例代码如下: 5. th:id th:id用于id的声明,类似于html标签中的id属性。示例代码如下:
6. th:if th:if用于条件判断,如果为否则标签不显示。示例代码如下:
… do something …
7. th:fragment th:fragment声明定义该属性的div为模板片段,常用于头文件、页尾文件的引入,常与th:include、th:replace一起使用。 假如在ch5_1的src/main/resources/templates目录下声明模板片段文件footer.html,具体代码如下: Insert title here
主体内容
清华大学出版社
那么,可以在ch5_1的src/main/resources/templates/index.html文件中引入模板片段,具体代码如下: Insert title here 测试Spring Boot的Thymeleaf支持
引入主体内容模板片段:
引入版权所有模板片段:
8. 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; public class LoginBean { String uname; String urole; //省略set和get方法 } 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.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import com.ch.ch5_1.model.LoginBean; @Controller public class LoginController { @RequestMapping("/toLogin") public String toLogin(Model model) { /*loginBean与login.html页面中的th:object="${loginBean}"相同,类似于Spring MVC的表单绑定。*/ model.addAttribute("loginBean", new LoginBean()); return "login"; } @RequestMapping("/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的代码如下: Insert title here

Form

Uname:

Urole:

页面result.html的代码如下: Insert title here

Result

继续提交 4) 运行 首先,运行Ch51Application主类。然后,访问http://localhost:8080/ch5_1/toLogin。运行结果如图5.6所示。 在图5.6的文本框中输入信息后,单击Sumbit按钮,打开如图5.7所示的页面。 图5.6页面login.html的运行结果 图5.7页面result.html的运行结果 9. th:src th:src用于外部资源引入,类似于

处理JSON数据

添加用户

3. 创建控制器 在ch5_2应用的com.ch.ch5_2.controller包中,创建控制器类TestJsonController。在该类中有两个处理方法,一个是界面导航方法input,一个是接收页面请求的方法。具体代码如下: package com.ch.ch5_2.controller; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ch.ch5_2.model.Person; @Controller public class TestJsonController { /** * 进入视图页面 */ @RequestMapping("/input") public String input() { return "input"; } /** * 接收页面请求的JSON数据 */ @RequestMapping("/testJson") @ResponseBody /*@RestController注解相当于@ResponseBody + @Controller合在一起的作用。 ①如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面或者html,返回的内容就是return的内容。 ②如果需要返回到指定页面,则需要用@Controller注解。如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。 */ public List> testJson(@RequestBody Person user) { //打印接收的JSON格式数据 System.out.println("pname=" + user.getPname() + ", password=" + user.getPassword() + ",page=" + user.getPage()); //返回Person对象 //return user; /**ArrayList allp = new ArrayList(); Person p1 = new Person(); p1.setPname("陈恒1"); p1.setPassword("123456"); p1.setPage(80); allp.add(p1); Person p2 = new Person(); p2.setPname("陈恒2"); p2.setPassword("78910"); p2.setPage(90); allp.add(p2); //返回ArrayList对象 return allp; **/ Map map = new HashMap(); map.put("pname", "陈恒2"); map.put("password", "123456"); map.put("page", 25); //返回一个Map对象 //return map; //返回一个List>对象 List> allp = new ArrayList>(); allp.add(map); Map map1 = new HashMap(); map1.put("pname", "陈恒3"); map1.put("password", "54321"); map1.put("page", 55); allp.add(map1); return allp; } } 4. 运行 首先,运行Ch52Application主类。然后,访问http://localhost:8080/ch5_2/input。运行效果如图5.15所示。 图5.15input.html运行效果 5.4Spring Boot文件上传与下载 视频讲解 文件上传与下载是Web应用开发中常用的功能之一。本节将讲解如何在Spring Boot的Web应用开发中实现文件的上传与下载。 在实际的Web应用开发中,为了成功上传文件,必须将表单的method设置为post,并将enctype设置为multipart/formdata。只有这样设置,浏览器才能将所选文件的二进制数据发送给服务器。 从Servlet 3.0开始,就提供了处理文件上传的方法,但这种文件上传需要在Java Servlet中完成,而Spring MVC提供了更简单的封装。Spring MVC是通过Apache Commons FileUpload技术实现一个MultipartResolver的实现类CommonsMultipartResolver完成文件上传的。因此,Spring MVC的文件上传需要依赖Apache Commons FileUpload组件。 Spring MVC将上传文件自动绑定到MultipartFile对象中,MultipartFile提供了获取上传文件内容、文件名等方法,并通过transferTo方法将文件上传到服务器的磁盘中。MultipartFile的常用方法如下。  byte[] getBytes(): 获取文件数据。  String getContentType(): 获取文件MIME类型,如image/jpeg等。  InputStream getInputStream(): 获取文件流。  String getName(): 获取表单中文件组件的名字。  String getOriginalFilename(): 获取上传文件的原名。  long getSize(): 获取文件的字节大小,单位为b(byte)。  boolean isEmpty(): 是否有(选择)上传文件。  void transferTo(File dest): 将上传文件保存到一个目标文件中。 Spring Boot的springbootstarterweb已经集成了Spring MVC,所以使用Spring Boot实现文件上传更加便捷,只需引入Apache Commons FileUpload组件依赖即可。 下面通过一个实例讲解Spring Boot文件上传与下载的实现过程。 【例57】Spring Boot文件上传与下载。 具体实现步骤如下。 1. 引入Apache Commons FileUpload组件依赖 在Web应用ch5_2的pom.xml文件中,添加Apache Commons FileUpload组件依赖。代码如下: commons-fileupload commons-fileupload 1.3.3 2. 设置上传文件大小限制 在Web应用ch5_2的配置文件application.properties中,添加如下配置限制上传文件大小。 #上传文件时,默认单个上传文件大小是1MB,max-file-size设置单个上传文件大小 spring.servlet.multipart.max-file-size=50MB #默认总文件大小是10MB,max-request-size设置总上传文件大小 spring.servlet.multipart.max-request-size=500MB 3. 创建选择文件视图页面 在ch5_2应用的src/main/resources/templates目录下,创建选择文件视图页面uploadFile.html。该页面中有一个enctype属性值为multipart/formdata的form表单。具体代码如下: Insert title here

文件上传示例

4. 创建控制器 在ch5_2应用的com.ch.ch5_2.controller包中,创建控制器类TestFileUpload。在该类中有4个处理方法,一个是界面导航方法uploadFile,一个是实现文件上传的upload方法,一个是显示将要被下载文件的showDownLoad方法,一个是实现下载功能的download方法。具体代码如下: package com.ch.ch5_2.controller; import java.io.File; import java.io.IOException; import java.net.URLEncoder; import javax.servlet.http.HttpServletRequest; import org.apache.commons.io.FileUtils; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity.BodyBuilder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; @Controller public class TestFileUpload { /** * 进入文件选择页面 */ @RequestMapping("/uploadFile") public String uploadFile() { return "uploadFile"; } /** * 上传文件自动绑定到MultipartFile对象中, * 在这里使用处理方法的形参接收请求参数。 */ @RequestMapping("/upload") public String upload( HttpServletRequest request, @RequestParam("description") String description, @RequestParam("myfile") MultipartFile myfile) throws IllegalStateException, IOException { System.out.println("文件描述:" + description); //如果选择了上传文件,将文件上传到指定的目录uploadFiles if(!myfile.isEmpty()) { //上传文件路径 String path = request.getServletContext().getRealPath("/uploadFiles/"); //获得上传文件原名 String fileName = myfile.getOriginalFilename(); File filePath = new File(path + File.separator + fileName); //如果文件目录不存在,创建目录 if(!filePath.getParentFile().exists()) { filePath.getParentFile().mkdirs(); } //将上传文件保存到一个目标文件中 myfile.transferTo(filePath); } //转发到一个请求处理方法,查询将要下载的文件 return "forward:/showDownLoad"; } /** * 显示要下载的文件 */ @RequestMapping("/showDownLoad") public String showDownLoad(HttpServletRequest request, Model model) { String path = request.getServletContext().getRealPath("/uploadFiles/"); File fileDir = new File(path); //从指定目录获得文件列表 File filesList[] = fileDir.listFiles(); model.addAttribute("filesList", filesList); return "showFile"; } /** * 实现下载功能 */ @RequestMapping("/download") public ResponseEntity download( HttpServletRequest request, @RequestParam("filename") String filename, @RequestHeader("User-Agent") String userAgent) throws IOException { //下载文件路径 String path = request.getServletContext().getRealPath("/uploadFiles/"); //构建将要下载的文件对象 File downFile = new File(path + File.separator + filename); //ok表示HTTP中的状态是200 BodyBuilder builder =ResponseEntity.ok(); //内容长度 builder.contentLength(downFile.length()); //application/octet-stream:二进制流数据(最常见的文件下载) builder.contentType(MediaType.APPLICATION_OCTET_STREAM); //使用URLEncoder.encode对文件名进行编码 filename = URLEncoder.encode(filename,"UTF-8"); /** * 设置实际的响应文件名,告诉浏览器文件要用于"下载"和"保存"。 * 不同的浏览器,处理方式不同,根据浏览器的实际情况区别对待。 */ if(userAgent.indexOf("MSIE") > 0) { //IE浏览器,只需要用UTF-8字符集进行URL编码 builder.header("Content-Disposition", "attachment; filename=" + filename); }else { /**非IE浏览器,如FireFox、Chrome等浏览器,则需要说明编码的字符集 * filename后面有一个*号,在UTF-8后面有两个单引号 */ builder.header("Content-Disposition", "attachment; filename*=UTF-8''" + filename); } return builder.body(FileUtils.readFileToByteArray(downFile)); } } 5. 创建文件下载视图页面 在ch5_2应用的src/main/resources/templates目录下,创建文件下载视图页面showFile.html。具体代码如下: Insert title here

文件下载示例

文件列表

6. 运行 首先,运行Ch52Application主类。然后,访问http://localhost:8080/ch5_2/uploadFile。运行效果如图5.16所示。 图5.16文件选择界面 在图5.16中输入文件描述,并选择上传文件后,单击“上传文件”按钮,实现文件上传。文件上传成功后,打开如图5.17所示的下载文件列表页面。 图5.17下载文件列表页面 单击图5.17中的文件名即可下载文件,至此,文件上传与下载示例演示完毕。 5.5Spring Boot的异常统一处理 视频讲解 在Spring Boot应用的开发中,不管是对底层数据库操作,对业务层操作,还是对控制层操作,都会不可避免地遇到各种可预知的、不可预知的异常需要处理。如果每个过程都单独处理异常,那么系统的代码耦合度高、工作量大且不好统一,以后维护的工作量也很大。 如果能将所有类型的异常处理从各层中解耦出来,则既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护。幸运的是,Spring框架支持这样的实现。本节将从自定义error页面、@ExceptionHandler注解以及@ControllerAdvice 3种方式讲解Spring Boot应用的异常统一处理。 5.5.1自定义error页面 在Spring Boot Web应用的src/main/resources/templates目录下添加error.html页面,访问发生错误或异常时,Spring Boot将自动找到该页面作为错误页面。Spring Boot为错误页面提供了以下属性。  timestamp: 错误发生时间;  status: HTTP状态码;  error: 错误原因;  exception: 异常的类名;  message: 异常消息(如果这个错误是由异常引起的);  errors: BindingResult异常里的各种错误(如果这个错误是由异常引起的);  trace: 异常跟踪信息(如果这个错误是由异常引起的);  path: 错误发生时请求的URL路径。 下面通过一个实例讲解在Spring Boot应用的开发中,如何使用自定义error页面。 【例58】自定义error页面。 具体实现步骤如下。 1. 创建基于Thymeleaf模板引擎的Spring Boot Web应用ch5_3 参照5.2.6节的例55,创建基于Thymeleaf模板引擎的Spring Boot Web应用ch5_3。 2. 设置Web应用ch5_3的上下文路径 在ch5_3的application.properties文件中配置如下内容: server.servlet.context-path=/ch5_3 3. 创建自定义异常类MyException 创建名为com.ch.ch5_3.exception的包,并在该包中创建名为MyException的异常类。具体代码如下: package com.ch.ch5_3.exception; public class MyException extends Exception { private static final long serialVersionUID = 1L; public MyException() { super(); } public MyException(String message) { super(message); } } 4. 创建控制器类TestHandleExceptionController 创建名为com.ch.ch5_3.controller的包,并在该包中创建名为TestHandleExceptionController的控制器类。在该控制器类中,有4个请求处理方法,一个是导航到index.html,另外3个分别抛出不同的异常(并没有处理异常)。具体代码如下: package com.ch.ch5_3.controller; import java.sql.SQLException; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import com.ch.ch5_3.exception.MyException; @Controller public class TestHandleExceptionController { @RequestMapping("/") public String index() { return "index"; } @RequestMapping("/db") public void db() throws SQLException { throw new SQLException("数据库异常"); } @RequestMapping("/my") public void my() throws MyException { throw new MyException("自定义异常"); } @RequestMapping("/no") public void no() throws Exception { throw new Exception("未知异常"); } } 5. 整理脚本样式静态文件 JS脚本、CSS样式、图片等静态文件默认放置在src/main/resources/static目录下,ch5_3应用引入了与ch5_2一样的BootStrap和jQuery。 6. View视图页面 Thymeleaf模板默认将视图页面放在src/main/resources/templates目录下。因此,我们在src/main/resources/templates目录下新建html页面文件index.html和error.html。 在index.html页面中,有4个超链接请求,3个请求在控制器中有对应处理,另一个请求是404错误。具体代码如下: index

异常处理示例

在error.html页面中,使用Spring Boot为错误页面提供的属性显示错误消息。具体代码如下: error

非常抱歉,没有找到您要查看的页面

7. 运行 首先,运行Ch53Application主类。然后,访问http://localhost:8080/ch5_3/打开index.html页面,运行效果如图5.18所示。 单击图5.18中的超链接时,Spring Boot应用将根据链接请求,到控制器中找对应的处理。例如,单击图5.18中的“处理数据库异常”链接时,将执行控制器中的public void db() throws SQLException方法,而该方法仅仅抛出了SQLException异常,并没有处理异常。当Spring Boot发现有异常抛出且没有处理时,将自动在src/main/resources/templates目录下找到error.html页面显示异常信息,效果如图5.19所示。 图5.18index.html页面 图5.19error.html页面 从上述例58的运行结果可以看出,使用自定义error页面并没有真正处理异常,只是将异常或错误信息显示给客户端,因为在服务器控制台上同样抛出了异常,如图5.20所示。 图5.20异常信息 5.5.2@ExceptionHandler注解 在5.5.1节中使用自定义error页面并没有真正处理异常,在本节可以使用@ExceptionHandler注解处理异常。如果在Controller中有一个使用@ExceptionHandler注解修饰的方法,那么当Controller的任何方法抛出异常时,都由该方法处理异常。 下面通过实例讲解如何使用@ExceptionHandler注解处理异常。 【例59】使用@ExceptionHandler注解处理异常。 具体实现步骤如下。 1. 在控制器类中添加使用@ExceptionHandler注解修饰的方法 在例58的控制器类TestHandleExceptionController中,添加一个使用@ExceptionHandler注解修饰的方法,具体代码如下: @ExceptionHandler(value=Exception.class) public String handlerException(Exception e) { //数据库异常 if (e instanceof SQLException) { return "sqlError"; } else if (e instanceof MyException) {   //自定义异常 return "myError"; } else {   //未知异常 return "noError"; } } 2. 创建sqlError、myError和noError页面 在ch5_3的src/main/resources/templates目录下,创建sqlError、myError和noError页面。当发生SQLException异常时,Spring Boot处理后,显示sqlError页面; 当发生MyException异常时,Spring Boot处理后,显示myError页面; 当发生未知异常时,Spring Boot处理后,显示noError页面。具体代码略。 3. 运行 再次运行Ch53Application主类后,访问http://localhost:8080/ch5_3/打开index.html页面,单击“处理数据库异常”链接时,执行控制器中的public void db() throws SQLException方法,该方法抛出了SQLException,这时Spring Boot会自动执行使用@ExceptionHandler注解修饰的方法public String handlerException(Exception e)进行异常处理并打开sqlError.html页面,同时观察控制台有没有抛出异常信息。注意单击“404错误”链接时,还是由自定义error页面显示错误信息,这是因为没有执行控制器中抛出异常的方法,进而不会执行使用@ExceptionHandler注解修饰的方法。 从例59可以看出,在控制器中添加使用@ExceptionHandler注解修饰的方法才能处理异常。而一个Spring Boot应用中往往存在多个控制器,不太适合在每个控制器中添加使用@ExceptionHandler注解修饰的方法进行异常处理。可以将使用@ExceptionHandler注解修饰的方法放到一个父类中,然后所有需要处理异常的控制器继承该类即可。例如,可以将例59中使用@ExceptionHandler注解修饰的方法移到一个父类BaseController中,然后让控制器类TestHandleExceptionController继承该父类即可处理异常。 5.5.3@ControllerAdvice注解 使用5.5.2节中父类Controller进行异常处理,也有其自身的缺点,那就是代码耦合性太高。可以使用@ControllerAdvice注解降低这种父子耦合关系。 @ControllerAdvice注解,顾名思义,是一个增强的Controller。使用该Controller,可以实现3个方面的功能: 全局异常处理、全局数据绑定以及全局数据预处理。本节将学习如何使用@ControllerAdvice注解进行全局异常处理。 使用@ControllerAdvice注解的类是当前Spring Boot应用中所有类的统一异常处理类,该类中使用@ExceptionHandler注解的方法统一处理异常,不需要在每个Controller中逐一定义异常处理方法,这是因为对所有注解了@RequestMapping的控制器方法有效。 下面通过实例讲解如何使用@ControllerAdvice注解进行全局异常处理。 【例510】使用@ControllerAdvice注解进行全局异常处理。 具体实现步骤如下。 1. 创建使用@ControllerAdvice注解的类 在ch5_3的com.ch.ch5_3.controller包中,创建名为GlobalExceptionHandlerController的类。使用@ControllerAdvice注解修饰该类,并将例59中使用@ExceptionHandler注解修饰的方法移到该类中,具体代码如下: package com.ch.ch5_3.controller; import java.sql.SQLException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import com.ch.ch5_3.exception.MyException; @ControllerAdvice public class GlobalExceptionHandlerController { @ExceptionHandler(value=Exception.class) public String handlerException(Exception e) { //数据库异常 if (e instanceof SQLException) { return "sqlError"; } else if (e instanceof MyException) {   //自定义异常 return "myError"; } else {   //未知异常 return "noError"; } } } 2. 运行 再次运行Ch53Application主类后,访问http://localhost:8080/ch5_3/打开index.html页面测试即可。 5.6Spring Boot对JSP的支持 视频讲解 尽管Spring Boot建议使用HTML完成动态页面,但也有部分Java Web应用使用JSP完成动态页面。遗憾的是Spring Boot官方不推荐使用JSP技术,但考虑到是常用的技术,本节将介绍Spring Boot如何集成JSP技术。 下面通过实例讲解Spring Boot如何集成JSP技术。 【例511】Spring Boot集成JSP技术。 具体实现步骤如下。 1. 创建Spring Boot Web应用ch5_4 选择菜单File|New|Spring Starter Project,打开New Spring Starter Project对话框,在该对话框中选择和输入相关信息,如图5.21所示。 图5.21创建Spring Boot Web应用ch5_4 单击图5.21中的Next按钮,打开New Spring Starter Project Dependencies对话框,在该对话框中,选择Spring Web Starter依赖,如图5.22所示。 图5.22选择Spring Web Starter依赖 单击图5.22中的Finish按钮,完成创建Spring Boot Web应用ch5_4。 2. 修改pom.xml文件,添加Servlet、Tomcat和JSTL依赖 因为在JSP页面中使用EL和JSTL标签显示数据,所以在pom.xml文件中,除了添加Servlet和Tomcat依赖外,还需要添加JSTL依赖。具体代码如下: javax.servlet javax.servlet-api provided org.springframework.boot spring-boot-starter-tomcat provided org.apache.tomcat.embed tomcat-embed-jasper provided javax.servlet jstl 3. 设置Web应用ch5_4的上下文路径及页面配置信息 在ch5_4的application.properties文件中配置如下内容: server.servlet.context-path=/ch5_4 #设置页面前缀目录 spring.mvc.view.prefix=/WEB-INF/jsp/ #设置页面后缀 spring.mvc.view.suffix=.jsp 4. 创建实体类Book 创建名为com.ch.ch5_4.model的包,并在该包中创建名为Book的实体类。此实体类用在模板页面展示数据,代码与例55中的Book一样,不再赘述。 5. 创建控制器类ThymeleafController 创建名为com.ch.ch5_4.controller的包,并在该包中创建名为ThymeleafController的控制器类。在该控制器类中,实例化Book类的多个对象,并保存到集合ArrayList中。代码与例55中的ThymeleafController一样,不再赘述。 6. 整理脚本样式静态文件 JS脚本、CSS样式、图片等静态文件默认放置在src/main/resources/static目录下,ch5_4应用引入的BootStrap和jQuery与例55中的一样,不再赘述。 7. View视图页面 从application.properties配置文件中可知,将JSP文件路径指定到/WEBINF/jsp/目录。因此,我们需要在src/main目录下创建目录webapp/WEBINF/jsp/,并在该目录下创建JSP文件index.jsp。代码如下: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> JSP测试

第一个基于JSP技术的Spring Boot Web应用

图书列表

图书封面

${aBook.bname}

${aBook.author}

${aBook.isbn}

${aBook.price}

${aBook.publishing}

图书封面

${book.bname}

${book.author}

${book.isbn}

${book.price}

${book.publishing}

8. 运行 首先,运行Ch54Application主类。然后,访问http://localhost:8080/ch5_4/。运行效果如图5.23所示。 图5.23例511运行结果 5.7本章小结 本章首先介绍了Spring Boot的Web开发支持,然后详细讲述了Spring Boot推荐使用的Thymeleaf模板引擎,包括Thymeleaf的基础语法、常用属性以及国际化。同时,本章还介绍了Spring Boot对JSON数据的处理、文件上传下载、异常统一处理和对JSP的支持等Web应用开发的常用功能。 习题5 使用Hibernate Validator验证如图5.24所示的表单信息,具体要求如下: (1) 用户名必须输入,并且长度范围为5~20。 (2) 年龄范围为18~60。 (3) 工作日期在系统时间之前。 图5.24输入页面