SpringBoot(二)-JSP、Thymeleaf、视图映射

SpringBoot中常用的页面模板有:JSP、Freemarker、Thymeleaf(推荐)

SpringBoot集成使用JSP

  1. 新建子项03_jsp
  2. 在pom.xml中添加JSP依赖如下:

     <dependencies>
         <dependency>
             <groupId>org.apache.tomcat.embed</groupId>
             <artifactId>tomcat-embed-jasper</artifactId>
         </dependency>
     </dependencies>
    
  3. 配置视图的前缀、后缀、热部署
    1. 在resources下添加一个application.yml

       #配置前后最
       spring:
         mvc:
           view:
             # jsp放在了webapp根路径下
             prefix: /
             suffix: .jsp
       #jsp的热部署,修改jsp内容会自动修改
       server:
         servlet:
           jsp:
             init-parameters:
               - developement: true
      
    2. 添加webapp路径

      1. 点击右上角Project Structure
      2. 找到03_jsp->web->右边Web Resources Directories->点击下面的+,Web Resources Directory path路径后面添加/src/main/webapp,点击OK
      3. 在webapp下添加一个test.jsp
      4. 注意: springboot项目是不需要添加web.xml的,但是需要webapp来存放jsp文件
  4. 设置工作目录(Working directory)
    1. springboot集成jsp必须要设置工作目录
    2. 点击Eidt Configurations ->Spring Boot->Application(当前项目的启动入口类)->Working directory设置->点击右边的+加号->选择Macros下的ModuleFileDir->点击insert->点击OK
    3. 如果是使用Sring-boot-maven-plugin插件运行(在idea右侧的插件列表启动),可以不用设置
      1. 在父项目22_spparent的pom.xml中添加

         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-maven-plugin</artifactId>
             <version>2.3.4.RELEASE</version>
         </dependency>
        
  5. 控制器做如下改造

     //注意不能使用RestController
     @Controller
     public class TestController {
        
         @GetMapping("/index")
         public String index() {
             //会自动拼接前后缀, /test.jsp
             return "test";
         }
     }
    
  6. 运行项目,在浏览器输入http://localhost:8080/index

Thymeleaf

  1. Thymeleaf是在SpringBoot中推荐使用的页面模板引擎,可以完全取代JSP,可以在没有服务器的环境下展示静态页面(jsp本质是servlet因此必须运行依赖后台服务)
  2. 官网https://www.thymeleaf.org

简单使用

  1. 新建子项目04_thymeleaf
  2. pom.xml添加依赖

     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-thymeleaf</artifactId>
         </dependency>
     </dependencies>
    
  3. 一些默认配置:只要引入上面的库,相当于做了下面的配置

     spring:
       thymeleaf:
         #返回的数据编码
         encoding: UTF-8
         # 默认前后缀
         prefix: classpath:/templates/
         suffix: .html
         #开启thymeleaf模板功能
         enabled: true
         #返回的数据格式
         servlet:
           content-type: text/html
    
  4. resources下面新建一个templates文件夹
    1. 下面新建一个index.html,浏览器输入http://localhost:8080/templates/index.html但是无法打开
    2. templates中的资源必须通过转发才能访问,在控制器中做一个转发

       //注意不能使用RestController
       @Controller
       public class TestController {
           @GetMapping("/testIndex")
           public String testIndex() {
               // prefix: classpath:/templates/
               // suffix: .html
               //转发到的最终路径
               // classpath:/templates/index.html
               return "index";
           }
       }
      
      
    3. 浏览器输入http://localhost:8080/testIndex,这可以正常访问index.html

页面传值

  1. 控制器中转发如下

     //方法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";
     }
    
    1. Model参数并非SringBoot特有,可以单独在SringMVC中使用
  2. 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的静态资源访问

  1. classpath:/templates/目录下的内容,默认只能通过转发访问
  2. 可以通过spring.resources.static-locations设置静态资源目录,可以直接通过URL访问
    1. 在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;
      
      
    2. 可以看到默认值是CLASSPATH_RESOURCE_LOCATIONS,也就是这些路径"classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" ,凡是这些路径下的资源都可以直接访问
    3. 因此在resources下新建一个static文件夹,在内部再创建一个test.html,然后运行项目
    4. 浏览器输入:http://localhost:8080/test.html则可以直接访问
    5. 当然也可以直接在application.yml中修改上面的默认路径,设置一个新的路径,只要将html文件放在这个新路径下,就可以直接访问

       spring:
         resources:
           static-locations: "设置新的路径"
      

Thymeleaf常用语法

  1. 属性名都是以th:开头
    1. th:text设置的文本不会进行解析渲染
    2. th:utext设置的文本会进行解析渲染,就是如果controller给html转发的数据是标签,会直接解析渲染
    3. 其他属性跟原来标签的属性相比,就是添加了一个th:,比如之前的属性是src,现在的属性就是th:src
  2. 常用表达式
    1. 变量表达式(Variable) : ${…}
      1. 传递到html中的变量取值方式,上面已讲
    2. 选中变量表达式(Selection Variable) : *{…}
    3. 消息表达式(Message) : #{…}
    4. 链接表达式(Link URL) : @{…}
    5. 片段表达式(Fragment) : ~{…}

注释

  1. HTML注释

     <!--HTML注释-->
    
  2. 解析器级别的注释(parser-level)
    1. <!--/*--><!--/*-->之间的任何内容
    2. 只有经过thymeleaf解析器处理后,才会变成真正的注释
     //通过thymeleaf解析这个时,会当做注释
     <!--/*-->
         <div>我是div2</div>
     <!--*/-->
    
  3. 原形注释(prototype-only)
    1. 作为静态页面打开时,它是注释
    2. 经过thymeleaf解析器处理后,它是正常标签,不是注释
    3. 跟上面相反
     //直接在浏览器打开会当做注释,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>

选中变量表达式

  1. 新建一个domain下的Person类

     //com.zh.domain.Person
     @Data
     //生成所有参数的构造方法
     @AllArgsConstructor
     //生成无参的构造方法
     @NoArgsConstructor
     public class Person {
         private Integer id;
         private String name;
     }
    
  2. TestController

     @GetMapping("/selection")
     public String selection(Model model) {
         //调用有参构造函数
         model.addAttribute("person", new Person(10, "MJ"));
         return "04_selection";
     }
    
  3. 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>
    

消息表达式

  1. 消息表达式可以实现国际化(internationalization,i18n)的功能
    1. i18n: 这个单词internationalization从i到n之间有18个字母,所以简称i18n
    2. application.yml中设置如下:

       spring:
         messages:
           # 关闭参考系统语言
           fallback-to-system-locale: false
           # 属性文件名(默认就是messages),就是设置系统默认的语言
           basename: i18n.messages
      
  2. 新建每个国家地区对应的属性文件
    1. 文件名格式是:basename_语言代码_国家地区.properties
    2. 语言代码+国家地区参考:https://www.douban.com/group/topic/4725265/
  3. 举例
    1. 在resources下新建一个i18n文件夹
    2. 下面新建一个messages.properties

       login=login0
       register=register0
      
    3. 新建一个messages_en_US.properties

       login=login1
       register=register1
      
    4. 新建一个messages_zh_CN.properties

       login=登录1
       register=注册1
      
    5. 以上三个properties会自动合并成一个Resource Bundle ‘messages’文件夹,如下图所示

      图1

    6. TestController

       @GetMapping("/i18n")
       public String i18n() {
           return "05_i18n";
       }
      
    7. 05_i18n.html如下

       <!--根据浏览器设置的语言,来获取相应properties的数据-->
       <div th:text="#{login}"></div>
       <div th:text="#{register}"></div>
      
    8. 在浏览器中设置相应的语言,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

  1. 仅仅实现循环,而且不带任何元素,跟微信小程序一样
<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>

内置对象

  1. 基础对象:

     #ctx
     #vars
     #locale
     #request:HttpServletRequest
     #response:HttpServletResponse
     #session、session: HttpSession
     #serveltContext、application:ServletContext
    
  2. 工具对象

     #execInfo、#messages、#uris、#conbersions
     #dates、#calendars
     #numbes、#strings、#objects、#bools
     #arrays、#llists、#sets、#maps
     #aggregates、#ids
    
  3. 举例使用:

    1. 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";
       }
      
    2. 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的视图映射

  1. 默认情况下,SpringBoot会把index视图当做是首页(classpath:/templates/index.html)
  2. 可以通过WebMvcConfigure进行视图映射,简化Controller代码
    1. 新建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");
           }
       }
      
    2. TestController

       @Controller
       public class TestController {
           //在SpringMVCConfig配置了,因此这里不需要了
           //    @GetMapping("/comment")
           //    public String comment() {
           //        return "02_comment";
           //    }
       }
      
Table of Contents