天天看點

Spring Boot與Thymeleaf內建

JSP在内嵌Servlet容器上運作有些問題(内嵌Tomcate、Jetty不支援以jar的形式運作JSP,Undertow不支援JSP)。

Spring Boot 預設使用Thymeleaf模闆,因為Thymeleaf模闆提供了完美的Spring MVC支援,可以完全替代JSP。

1 Thymeleaf基礎知識

Thymeleaf 是一個跟 Velocity、FreeMarker 類似的模闆引擎,它可以完全替代 JSP 。相較與其他的模闆引擎,它有如下三個極吸引人的特點:

  1. Thymeleaf 在有網絡和無網絡的環境下皆可運作,即它可以讓美工在浏覽器檢視頁面的靜态效果,也可以讓程式員在伺服器檢視帶資料的動态頁面效果。這是由于它支援 html 原型,然後在 html 标簽裡增加額外的屬性來達到模闆+資料的展示方式。浏覽器解釋 html 時會忽略未定義的标簽屬性,是以 thymeleaf 的模闆可以靜态地運作;當有資料傳回到頁面時,Thymeleaf 标簽會動态地替換掉靜态内容,使頁面動态顯示。
  2. Thymeleaf 開箱即用的特性。它提供标準和spring标準兩種方言,可以直接套用模闆實作JSTL、 OGNL表達式效果,避免每天套模闆、該jstl、改标簽的困擾。同時開發人員也可以擴充和建立自定義的方言。
  3. Thymeleaf 提供spring标準方言和一個與 SpringMVC 完美內建的可選子產品,可以快速的實作表單綁定、屬性編輯器、國際化等功能。

Thymeleaf 是一個Java類庫,它是一個xml/xhtml/html5的模闆引擎,可以作為MVC的Web應用的View層。

示例

<html xmlns:th="http://www.thymeleaf.org"><!-- 通過這個命名空間将靜态視圖轉換成動态的,需要動态的處理的元素前面加上th: -->
  <head>
     <meta content="text/html;charset=UTF-8"/>
     <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
     <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link th:href="@{bootstrap/css/bootstrap.min.css}" rel="stylesheet"/> <!-- 通過@{}來對靜态資源的引用 -->
    <link th:href="@{bootstrap/css/bootstrap-theme.min.css}" rel="stylesheet"/>
  </head>
  <body>
  
  <div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">通路model</h3>
    </div>
    <div class="panel-body">
        <!-- ${}通路Mode中的屬性(注解要加上th: 字首) -->
          <span th:text="${singlePerson.name}"></span> 
    </div>
  </div>
  
  <div th:if="${not #lists.isEmpty(people)}"> <!-- if判斷(<、>、<=、>=、==、 !=、也支援SpringEL) -->
    <div class="panel panel-primary">
      <div class="panel-heading">
          <h3 class="panel-title">清單</h3>
      </div>
      <div class="panel-body">
          <ul class="list-group">
            <!-- 疊代 -->
        <li class="list-group-item" th:each="person:${people}">
            <span th:text="${person.name}"></span>
            <span th:text="${person.age}"></span>
            <button class="btn" th:onclick="'getName(\'' + ${person.name} + '\');'">獲得名字</button>
        </li>
          </ul>
      </div>
   </div>
 </div>
  
  <script th:src="@{jquery-1.10.2.min.js}" type="text/javascript"></script><!-- 2 -->
  <script th:src="@{bootstrap/js/bootstrap.min.js}"></script><!-- 2 -->
  
  <script th:inline="javascript"> // th:inline="javascript" 添加到script标簽可以通路mdel中的屬性
    var single = [[${singlePerson}]];
    console.log(single.name+"/"+single.age)
    
    function getName(name){
      console.log(name);
    }
  </script>
  
  </body>
 </html>      

2 與Spring MVC的內建

在Spring MVC中我們如果要內建一個模闆引擎的話,需要定義ViewResolver,而ViewResolver需要定義一個View,如我們為jsp定義的ViewResolver:

@Bean
public InternalResourceViewResolver viewResolver() {
  InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
  viewResolver.setPrefix("/WEB-INF/classes/views/");// 運作時代碼會将頁面自動編譯到/WEB-INF/classes/views/下
  viewResolver.setSuffix(".jsp");
  viewResolver.setViewClass(JstlView.class);
  return viewResolver;
}      

使用JstlView定義了一個InternalResourceViewResolver。是以使用Thymeleaf作為我的模闆也需要做類似的定義。Thymeleaf為我們提供了org.thymeleaf.spring4.view.ThymeleafView和org.thymeleaf.spring4.view.ThymeleafViewResolver(預設使用ThymeleafView作為View)。Thymeleaf為我們提供了SpringTemplateEngine類,用來驅動在Spring MVC下使用Thymeleaf模闆引擎,另外還提供了一個TemplateResolver類來設定通用的模闆引擎(字首、字尾等)。如下:

@Bean
public TemplateResolver templateResolver() {
  TemplateResolver templateResolver = new ServletContextTemplateResolver();
  templateResolver.setPrefix("/WEB-INF/templates/");
  templateResolver.setSuffix(".html");
  templateResolver.setTemplateMode("HTML5");
  return templateResolver;
}

@Bean
public SpringTemplateEngine templateEngine() {
  SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
  springTemplateEngine.setTemplateResolver(templateResolver());
  return springTemplateEngine;
}

@Bean
public ThymeleafViewResolver thymeleafViewEngine() {
  ThymeleafViewResolver thymeleafViewResolver = new ThymeleafViewResolver();
  thymeleafViewResolver.setTemplateEngine(templateEngine());
  // thymeleafViewResolver.setViewClass(ThymeleafView.class);
  return thymeleafViewResolver;
}      

3 Spring Boot對Thymeleaf的支援

Spring Boot通過org.springframework.boot.autoconfigure.thymeleaf包對thymeleaf進行自動配置。如圖:

Spring Boot與Thymeleaf內建

通過ThymeleafAutoConfiguration對內建所需的Bean進行自動配置,包括templateResolver、templateEngine和thymeleafResolver的配置。

通過ThymeleafProperties配置Thymeleaf,再application.properties檔案中以spring.thymeleaf開頭,通過源碼我們可以看出如何設定屬性和預設值:

package org.springframework.boot.autoconfigure.thymeleaf;

import java.nio.charset.Charset;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.MimeType;

@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

  private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");

  private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");

  public static final String DEFAULT_PREFIX = "classpath:/templates/";

  public static final String DEFAULT_SUFFIX = ".html";

  private boolean checkTemplate = true;

  private boolean checkTemplateLocation = true;
  
  /**
   * 字首設定,Spring Boot預設模闆,放置在classpath:/templates/目錄下
   */
  private String prefix = DEFAULT_PREFIX;
  
  /**
   * 字尾設定,預設是".html"
   */
  private String suffix = DEFAULT_SUFFIX;
  
  /**
   * 模闆模式設定,預設是"HTML5"
   */
  private String mode = "HTML5";
  
  /**
   * 模闆編碼設定,預設是"UTF-8"
   */
  private Charset encoding = DEFAULT_ENCODING;
  
  /**
   * 模闆媒體類型設定,預設是"text/html"
   */
  private MimeType contentType = DEFAULT_CONTENT_TYPE;
  
  /**
   * 是否開啟模闆緩存,預設是開啟,開發時請關閉
   */
  private boolean cache = true;

  /**
   * Comma-separated list of view names that can be resolved.
   */
  private String[] viewNames;

  /**
   * Comma-separated list of view names that should be excluded from resolution.
   */
  private String[] excludedViewNames;

  /**
   * Enable MVC Thymeleaf view resolution.
   */
  private boolean enabled = true;

  ...

}      

3.1 示例Bean

package com.chenfeng.xiaolyuh.entity;

public class Person {
  private String name;
  private Integer age;
  
  public Person() {
    super();
  }
  public Person(String name, Integer age) {
    super();
    this.name = name;
    this.age = age;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public Integer getAge() {
    return age;
  }
  public void setAge(Integer age) {
    this.age = age;
  }

}      

3.2 腳本樣式靜态檔案

靜态資源放在/src/main/resources/static目錄下,如圖:

Spring Boot與Thymeleaf內建

3.3 示範頁面 頁面預設放在/src/main/resources/templates,在該檔案下建立index.html檔案:

<html xmlns:th="http://www.thymeleaf.org"><!-- 通過這個命名空間将靜态視圖轉換成動态的,需要動态的處理的元素前面加上th: -->
  <head>
     <meta content="text/html;charset=UTF-8"/>
     <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
     <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link th:href="@{bootstrap/css/bootstrap.min.css}" rel="stylesheet"/> <!-- 通過@{}來對靜态資源的引用 -->
    <link th:href="@{bootstrap/css/bootstrap-theme.min.css}" rel="stylesheet"/>
  </head>
  <body>
  
  <div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">通路model</h3>
    </div>
    <div class="panel-body">
        <!-- ${}通路Mode中的屬性(注解要加上th: 字首) -->
          <span th:text="${singlePerson.name}"></span> 
    </div>
  </div>
  
  <div th:if="${not #lists.isEmpty(people)}">
    <div class="panel panel-primary">
      <div class="panel-heading">
          <h3 class="panel-title">清單</h3>
      </div>
      <div class="panel-body">
          <ul class="list-group">
            <!-- 疊代 -->
        <li class="list-group-item" th:each="person:${people}">
            <span th:text="${person.name}"></span>
            <span th:text="${person.age}"></span>
            <button class="btn" th:onclick="'getName(\'' + ${person.name} + '\');'">獲得名字</button>
        </li>
          </ul>
      </div>
   </div>
 </div>
  
  <script th:src="@{jquery-1.10.2.min.js}" type="text/javascript"></script><!-- 2 -->
  <script th:src="@{bootstrap/js/bootstrap.min.js}"></script><!-- 2 -->
  
  <script th:inline="javascript"> // th:inline="javascript" 添加到script标簽可以通路mdel中的屬性
    var single = [[${singlePerson}]];
    console.log(single.name+"/"+single.age)
    
    function getName(name){
      console.log(name);
    }
  </script>
  
  </body>
</html>      

3.4 控制層代碼

package com.chenfeng.xiaolyuh;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.chenfeng.xiaolyuh.entity.Person;
import com.chenfeng.xiaolyuh.properties.AuthorSettings;

@SpringBootApplication
@Controller
public class SpringBootStudentApplication {
  
  @RequestMapping("index")
  public String index(Model model){
    Person single = new Person("aa",11);
    
    List<Person> people = new ArrayList<Person>();
    Person p1 = new Person("xx",11);
    Person p2 = new Person("yy",22);
    Person p3 = new Person("zz",33);
    people.add(p1);
    people.add(p2);
    people.add(p3);
    
    model.addAttribute("singlePerson", single);
    model.addAttribute("people", people);
    
    return "index";
  }

  // 标準的JAVA應用main方法,主要作用作為項目啟動的入口
  public static void main(String[] args) {
    SpringApplication.run(SpringBootStudentApplication.class, args);
  }

}