SpringBoot(二)-JSP、Thymeleaf、视图映射
SpringBoot中常用的页面模板有:JSP、Freemarker、Thymeleaf(推荐)
SpringBoot集成使用JSP
- 新建子项03_jsp
-
在pom.xml中添加JSP依赖如下:
<dependencies> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> </dependencies>
- 配置视图的前缀、后缀、热部署
-
在resources下添加一个application.yml
#配置前后最 spring: mvc: view: # jsp放在了webapp根路径下 prefix: / suffix: .jsp #jsp的热部署,修改jsp内容会自动修改 server: servlet: jsp: init-parameters: - developement: true
-
添加webapp路径
- 点击右上角Project Structure
- 找到03_jsp->web->右边Web Resources Directories->点击下面的+,Web Resources Directory path路径后面添加
/src/main/webapp
,点击OK - 在webapp下添加一个test.jsp
- 注意: springboot项目是不需要添加web.xml的,但是需要webapp来存放jsp文件
-
- 设置工作目录(Working directory)
- springboot集成jsp必须要设置工作目录
- 点击Eidt Configurations ->Spring Boot->Application(当前项目的启动入口类)->Working directory设置->点击右边的+加号->选择Macros下的ModuleFileDir->点击insert->点击OK
- 如果是使用Sring-boot-maven-plugin插件运行(在idea右侧的插件列表启动),可以不用设置
-
在父项目22_spparent的pom.xml中添加
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.4.RELEASE</version> </dependency>
-
-
控制器做如下改造
//注意不能使用RestController @Controller public class TestController { @GetMapping("/index") public String index() { //会自动拼接前后缀, /test.jsp return "test"; } }
- 运行项目,在浏览器输入
http://localhost:8080/index
Thymeleaf
- Thymeleaf是在SpringBoot中推荐使用的页面模板引擎,可以完全取代JSP,可以在没有服务器的环境下展示静态页面(jsp本质是servlet因此必须运行依赖后台服务)
- 官网https://www.thymeleaf.org
简单使用
- 新建子项目04_thymeleaf
-
pom.xml添加依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies>
-
一些默认配置:只要引入上面的库,相当于做了下面的配置
spring: thymeleaf: #返回的数据编码 encoding: UTF-8 # 默认前后缀 prefix: classpath:/templates/ suffix: .html #开启thymeleaf模板功能 enabled: true #返回的数据格式 servlet: content-type: text/html
- resources下面新建一个templates文件夹
- 下面新建一个index.html,浏览器输入
http://localhost:8080/templates/index.html
但是无法打开 -
templates中的资源必须通过转发才能访问,在控制器中做一个转发
//注意不能使用RestController @Controller public class TestController { @GetMapping("/testIndex") public String testIndex() { // prefix: classpath:/templates/ // suffix: .html //转发到的最终路径 // classpath:/templates/index.html return "index"; } }
- 浏览器输入
http://localhost:8080/testIndex
,这可以正常访问index.html
- 下面新建一个index.html,浏览器输入
页面传值
-
控制器中转发如下
//方法1: @GetMapping("/testIndex") public String testIndex(Model model) { model.addAttribute("name", "ZH"); model.addAttribute("age", 22); //向index.html传值 return "index"; } //方法二: @GetMapping("/testIndex") public String testIndex(HttpServletRequest request) { //向index.html传值 request.setAttribute("name", "ZH"); request.setAttribute("age", 20); return "index"; }
- Model参数并非SringBoot特有,可以单独在SringMVC中使用
-
index.html如下:
<!DOCTYPE html> <!--需要导入这个命名空间--> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> This is index.html <div th:text="${name}"></div> <div th:text="${age}"></div> </body> </html>
Springboot的静态资源访问
- classpath:/templates/目录下的内容,默认只能通过转发访问
- 可以通过spring.resources.static-locations设置静态资源目录,可以直接通过URL访问
-
在application.yml文件中输入static-locations,然后点击进入查看源码,如下:
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" }; /** * Locations of static resources. Defaults to classpath:[/META-INF/resources/, * /resources/, /static/, /public/]. */ private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
- 可以看到默认值是CLASSPATH_RESOURCE_LOCATIONS,也就是这些路径
"classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/"
,凡是这些路径下的资源都可以直接访问 - 因此在resources下新建一个static文件夹,在内部再创建一个test.html,然后运行项目
- 浏览器输入:
http://localhost:8080/test.html
则可以直接访问 -
当然也可以直接在application.yml中修改上面的默认路径,设置一个新的路径,只要将html文件放在这个新路径下,就可以直接访问
spring: resources: static-locations: "设置新的路径"
-
Thymeleaf常用语法
- 属性名都是以th:开头
- th:text设置的文本不会进行解析渲染
- th:utext设置的文本会进行解析渲染,就是如果controller给html转发的数据是标签,会直接解析渲染
- 其他属性跟原来标签的属性相比,就是添加了一个
th:
,比如之前的属性是src,现在的属性就是th:src
- 常用表达式
- 变量表达式(Variable) : ${…}
- 传递到html中的变量取值方式,上面已讲
- 选中变量表达式(Selection Variable) : *{…}
- 消息表达式(Message) : #{…}
- 链接表达式(Link URL) : @{…}
- 片段表达式(Fragment) : ~{…}
- 变量表达式(Variable) : ${…}
注释
-
HTML注释
<!--HTML注释-->
- 解析器级别的注释(parser-level)
<!--/*-->
和<!--/*-->
之间的任何内容- 只有经过thymeleaf解析器处理后,才会变成真正的注释
//通过thymeleaf解析这个时,会当做注释 <!--/*--> <div>我是div2</div> <!--*/-->
- 原形注释(prototype-only)
- 作为静态页面打开时,它是注释
- 经过thymeleaf解析器处理后,它是正常标签,不是注释
- 跟上面相反
//直接在浏览器打开会当做注释,thymeleaf不会认为是注释 <!--/*/ <div>我是div3</div> /*/-->
字面量
<!--name=Larry,age=10-->
<!--Ilovecoding.-->
<div th:text="Ilovecoding."></div>
<!--变量与常量拼接-->
<!--Larry love coding.22-->
<div th:text="${name} + ' love coding.' + ${age}"></div>
<!--Larry love coding.-->
<div th:text="${name} + | love coding.|"></div>
<!--不能拼接-->
<!--${name} love coding.-->
<div th:text="'${name} love coding.'"></div>
<!--可以拼接-->
<!--Larry love coding.-->
<div th:text="|${name} love coding.|"></div>
<!--数字-->
<!--age=20-->
<!--0-->
<div th:text="${age} % 2"></div>
<!--2020-->
<div th:text="2020"></div>
<!--4040-->
<div th:text="2020 * 2"></div>
<!-布尔-->
<!--true-->
<div th:text="${age} % 2 == 0"></div>
<!--false-->
<div th:text="${age} <= 10"></div>
<hr>
局部变量、三目运算
<div th:text="${name} ? '存在name' : '不存在name'"></div>
<div th:with="id=10, salary=200, nickName='JJ'">
<div th:text="'id = ' + ${id}"></div>
<div th:text="'salary = ' + ${salary}"></div>
<div th:text="'nickName = ' + ${nickName}"></div>
</div>
<div th:class="${age % 2 == 0} ? 'blue' : 'red'">
div666
</div>
<div th:text="${age} ? ${age} : 'No Age'">
</div>
<!--等价于上面-->
<div th:text="${age} ?: 'No Age'">
</div>
<!--等价于${age == null} ? 'No Age':null-->
<div th:text="${age == null} ? 'No Age'">
</div>
选中变量表达式
-
新建一个domain下的Person类
//com.zh.domain.Person @Data //生成所有参数的构造方法 @AllArgsConstructor //生成无参的构造方法 @NoArgsConstructor public class Person { private Integer id; private String name; }
-
TestController
@GetMapping("/selection") public String selection(Model model) { //调用有参构造函数 model.addAttribute("person", new Person(10, "MJ")); return "04_selection"; }
-
templates下新建一个04_selection.html
<div> <div th:text="${person.id}"></div> <div th:text="${person.name}"></div> </div> <hr> <div th:object="${person}"> <div th:text="*{id}"></div> <div th:text="*{name}"></div> </div> <hr> <div> <div th:text="*{person.id}"></div> <div th:text="*{person.name}"></div> </div> <hr> <div th:object="${person}"> <div th:text="*{#object.id}"></div> <div th:text="${#object.name}"></div> </div>
消息表达式
- 消息表达式可以实现国际化(internationalization,i18n)的功能
- i18n: 这个单词internationalization从i到n之间有18个字母,所以简称i18n
-
application.yml中设置如下:
spring: messages: # 关闭参考系统语言 fallback-to-system-locale: false # 属性文件名(默认就是messages),就是设置系统默认的语言 basename: i18n.messages
- 新建每个国家地区对应的属性文件
- 文件名格式是:basename_语言代码_国家地区.properties
- 语言代码+国家地区参考:https://www.douban.com/group/topic/4725265/
- 举例
- 在resources下新建一个i18n文件夹
-
下面新建一个messages.properties
login=login0 register=register0
-
新建一个messages_en_US.properties
login=login1 register=register1
-
新建一个messages_zh_CN.properties
login=登录1 register=注册1
-
以上三个properties会自动合并成一个Resource Bundle ‘messages’文件夹,如下图所示
-
TestController
@GetMapping("/i18n") public String i18n() { return "05_i18n"; }
-
05_i18n.html如下
<!--根据浏览器设置的语言,来获取相应properties的数据--> <div th:text="#{login}"></div> <div th:text="#{register}"></div>
- 在浏览器中设置相应的语言,05_i18n.html会展示不同的语言,如果浏览器的语言设置的没有对应的properties,则使用messages.properties
链接表达式
<!--当前URL: http://localhost:8080/test/zh/link-->
<!--当前ContextPath:test-->
<!--
实际访问地址为:http://localhost:8080/test/zh/users/login
参考的是当前页面的地址,直接在zh后面拼接
->
<div><a th:href="@{users/login}">Page-relative</a></div>
<!--
实际访问地址为:http://localhost:8080/test/users/login
参考的是ContextPath的地址
-->
<div><a th: href="@{/users/login}">Context-relative</a></div>
<!--
实际访问地址为:http://localhost:8080/users/login
参考的是服务器的地址
-->
<div><a th: href="@{~/users/login}">Server-relative</a></div>
<!--
实际访问地址为:http://baidu.com
参考的是协议,然后直接拼接
-->
<div><a th:href="@{//baidu.com}">Protocol-relative</a></div>
<!--
实际访问地址为: http://localhost"8080/test/users/get?id=10&name=ZH&no=18
可以传参
-->
<div><a th:href="@{/users/get(id=${id},name=${name},no=18)}">get</a></div>
<!--实际访问地址为: http://localhost:8080/test/users/18/detail-->
<div><a th:href="@{/users/{no}/detail(no=18)}">detail</a></div>
条件判断
<!--id=20-->
<!--666-->
<div th:if="${id > 10}">666</div>
<!--888-->
<div th:unless="${id <= 10}">888</div>
<!--name=ZH-->
<!--div3-->
<div th:switch="${name}">
<div th:case="'test'">div1</div>
<div th:case="${id}">div2</div>
<div th:case="*">div3</div>
</div>
遍历
<table>
<tr>
<th>row</th>
<th>id</th>
<th>name</th>
</tr>
<!-- persons传递到当前页面的数组数据,status自定义,可以取值status.count/index/current/ev(奇数行) -->
<tr th:each="person, status : ${persons}">
<td th:text="${status.index}"></td>
<td th:text="${person.id}"></td>
<td th:text="${person.name}"></td>
</tr>
</table>
//或者这样
<tr th:each="person : ${persons}" th:object="${person}">
<td th:text="*{id}"></td>
<td th:text="*{name}"></td>
</tr>
block
- 仅仅实现循环,而且不带任何元素,跟微信小程序一样
<div>
<!--使用原型注释功能-->
<!--/*/ <th:block th:each="person : ${persons}"> /*/-->
<div th:text="${person.id}"></div>
<div th:text="${person.name}"></div>
<!--/*/ </th:block> /*/-->
</div>
<!--不使用注释-->
<div>
<!--使用原型注释功能-->
<th:block th:each="person : ${persons}">
<div th:text="${person.id}"></div>
<div th:text="${person.name}"></div>
</th:block>
</div>
属性设置
<div th:text="${name}"></div>
<div data-th-text="${name}"></div>
<form th:attr="action=@{/user/get}, method='get'"></form>
<!--等价于上面-->
<form data-th-attr="action=@{/user/get},method='get'"></form>
<!-- <div class="blue red green"></div>
<div class="red"
th:attrprepend="class='blue'"
th:attrappend="class=' green'"></div>
<!--页面取值的另外一种方式,可以不使用th:text-->
<div>[[ ${id} + 'abc' ]]</div>
内置对象
-
#ctx #vars #locale #request:HttpServletRequest #response:HttpServletResponse #session、session: HttpSession #serveltContext、application:ServletContext
-
#execInfo、#messages、#uris、#conbersions #dates、#calendars #numbes、#strings、#objects、#bools #arrays、#llists、#sets、#maps #aggregates、#ids
-
举例使用:
-
TestController
@GetMapping("/object") public String object(HttpServletRequest request, HttpSession session) { request.setAttribute("birthday", new Date()); request.setAttribute("name", "request_mj"); session.setAttribute("name", "session_mj"); session.getServletContext().setAttribute("name", "ctx_mj"); return "09_object"; }
-
09_object.html
<!--设置日期展示格式--> <div th:text="${#dates.format(birthday, 'yyyy-MM-dd HH:mm:ss')}"></div> <div th:text="${#request.getAttribute('name')}"></div> <div th:text="${name}"></div> <div th:text="${#session.getAttribute('name')}"></div> <div th:text="${session.name}"></div> <div th:text="${#servletContext.getAttribute('name')}"></div> <div th:text="${application.name}"></div>
-
Springboot的视图映射
- 默认情况下,SpringBoot会把index视图当做是首页(classpath:/templates/index.html)
- 可以通过WebMvcConfigure进行视图映射,简化Controller代码
-
新建SpringMVCConfig类
//com.zh.cfg.SpringMVCConfig @Configuration public class SpringMVCConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // 首页的映射,输入http://localhost:8080/ 的时候会默认访问login.html,而不是index.html registry.addViewController("/").setViewName("login"); // 其他页面的映射 //就是不需要在TestController中单独写一个监听方法了 registry.addViewController("/comment").setViewName("02_comment"); //防止用户多输入一个/访问 registry.addViewController("/comment/").setViewName("02_comment"); } }
-
TestController
@Controller public class TestController { //在SpringMVCConfig配置了,因此这里不需要了 // @GetMapping("/comment") // public String comment() { // return "02_comment"; // } }
-