Spring編譯時模板入門
1. 引言
在本教程中,我們將探討編譯時模板。我們將了解它們是什麼,並介紹一些可用於實現它們的程式庫。
2. 什麼是編譯時模板?
在編寫應用程式的過程中,我們經常會遇到需要使用各種形式的模板庫的情況。
通常情況下,我們需要在運行時載入並解析模板,然後提供要綁定的資料以產生最終結果。這種方法會增加開銷並引入一些額外的風險。例如,格式錯誤的範本或意外的資料可能要等到我們嘗試使用時才會被發現。
不過,有一些函式庫可以採用略有不同的方式來實現這一點。它們不是在運行時加載和解析模板,而是在構建時將其編譯成 Java 類,然後像處理普通 Java 程式碼一樣處理它們。這意味著如果模板格式錯誤,建置就會失敗;而且,我們通常可以確保在實際使用模板時編譯器的安全性。
此外,其中一些庫可以完全無需反射即可工作。這使得它們可以在反射機制不完全可用的環境中使用。例如,我們可以在 GraalVM 設定中使用這些函式庫,將應用程式建置並執行為原生鏡像。
3. JStachio
JStachio是一個實作了 Mustache 範本語言的小型函式庫。然而,它是在編譯時而非運行時實現的。它也不使用運行時反射,因此適合與 GraalVM 之類的框架一起使用。
3.1. 依賴關係
要使用 JStachio,我們需要在建置過程中對其進行配置。它是一個編譯器註解處理器,可以自動將我們的模板轉換為 Java 程式碼。要使用它,我們需要在建置中包含最新版本。
如果我們使用 Maven,可以將以下程式碼加入pom.xml檔案中:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.jstach</groupId>
<artifactId>jstachio-apt</artifactId>
<version>1.3.7</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
我們還需要引入一個運行時依賴項才能使用 JStachio 模板:
<dependency>
<groupId>io.jstach</groupId>
<artifactId>jstachio</artifactId>
<version>1.3.7</version>
</dependency>
現在,我們已經準備好在我們的應用程式中使用它了。
3.2. 寫作模板
設定好 JStachio 之後,我們就可以開始寫模板了。模板由兩部分組成:模板本身和一個表示模板所需資料的 Java 類別。
我們使用 Mustache 模板語言編寫模板:
<html>
<body>
Hello, {{name}}!
</body>
</html>
接下來,我們需要一個表示模板的 Java 類別:
@JStache(path = "templates/jstachio.mustache")
public record JStachioModel(String name) { }
該類別上的JStache註解指示了應使用的範本。 Java編譯器隨後會偵測到該模板,並在同一套件中產生一個JStachioModelRenderer類別。
3.3.渲染模板
將模板建置成 Java 程式碼後,我們就可以使用它來渲染輸出。這可以透過of()方法來實現,該方法需要提供輸入模型和一個用於寫入輸出的StringBuilder物件:
JStachioModel model = new JStachioModel("Baeldung");
StringBuilder sb = new StringBuilder();
JStachioModelRenderer.of().execute(model, sb);
我們產生的JStachioModelRenderer需要我們的模型類別 JStachioModel 的一個實例,以確保在編譯時正確無誤。
3.4. 與 Spring 一起使用
除了手動使用 JStachio 之外,它還提供了一個 Spring Boot starter,讓我們可以從控制器中將其用作視圖技術。
我們只需要在建置過程中新增對應的依賴項:
<dependency>
<groupId>io.jstach</groupId>
<artifactId>jstachio-spring-boot-starter-webmvc</artifactId>
<version>1.3.7</version>
</dependency>
完成這些步驟後,我們就可以讓 Spring 控制器傳回一個JStachioModelView實例,該實例本身包裝了我們的模型類別:
@GetMapping("/jstachio")
public View get() {
return JStachioModelView.of(new JStachioModel("Baeldung"));
}
這樣就足以讓JStachio使用提供的資料渲染對應的範本:
$ http localhost:8080/jstachio
HTTP/1.1 200
Content-Length: 55
Content-Type: text/html;charset=UTF-8
<html>
<body>
Hello, Baeldung!
</body>
</html>
4. 搖滾樂
Rocker是另一個可用於範本的函式庫。它使用自訂模板語言而不是 Mustache,但在建置時仍然會編譯成 Java 程式碼。不過,Rocker 使用了一些反射機制,因此在某些情況下可能不太適用。
4.1. 依賴關係
要使用 Rocker,我們需要在建造過程中對其進行配置。它是一個自訂的 Maven 插件,可以自動將我們的模板轉換為 Java 程式碼。要使用它,我們需要在建置中包含最新版本,目前是 2.4.0 。
<plugin>
<groupId>com.fizzed</groupId>
<artifactId>rocker-maven-plugin</artifactId>
<version>2.4.0</version>
<executions>
<execution>
<id>generate-rocker-templates</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<templateDirectory>src/main/resources/templates/rocker</templateDirectory>
<outputDirectory>target/rocker</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
這段程式碼會在src/main/resources/templates/rocker下搜尋模板,找到所有副檔名為.rocker.html的檔案。然後,它會將產生的 Java 程式碼寫入target/rocker 。預設情況下,它會將此目錄新增為建置來源,從而確保程式碼能夠編譯到我們的應用程式中。
此外,我們還需要運行時依賴項才能使其正常運作:
<dependency>
<groupId>com.fizzed</groupId>
<artifactId>rocker-runtime</artifactId>
<version>2.4.0</version>
</dependency>
現在,我們已經準備好在我們的應用程式中使用它了。
4.2. 寫作模板
設定完成後,就可以使用了。與 JStachio 不同,Rocker 只使用模板作為實際輸入,但它可以引用我們為資料編寫的其他 Java 類別。
我們使用自訂標記語言編寫模板,這樣就可以指定輸入綁定以及如何使用它們:
@import com.baeldung.templates.RockerModel
@args(RockerModel model)
<html>
<head>
</head>
<body>
<h1>Demo</h1>
<p>Hello @model.name()!</p>
</body>
</html>
在這種情況下,我們導入了一個必須已經存在的類別com.baeldung.templates.RockerModel ,我們將其指定為模板的一個參數,然後在渲染模板時引用它。
我們的模型類別是指模板編譯時存在於類別路徑中的任何類別:
public record RockerModel(String name) { }
4.3.渲染模板
編譯完成後,我們就可以使用模板來產生輸出。 Rocker並沒有直接引用已編譯的模板,而是提供了透過模板名稱來引用它們的方法:
BindableRockerModel template = Rocker.template("RockerDemo.rocker.html");
template.bind("model", new RockerModel("Baeldung"));
RockerOutput output = template.render();
ContentType contentType = output.getContentType();
Charset charset = output.getCharset();
String html = output.toString();
它會自動找到為我們的RockerDemo.rocker.html模板產生的類,並使用提供的模型物件進行渲染。然後,我們的output變數會提供渲染後的模板,以及一些額外資訊,例如內容類型和字元集。
這樣做的一個缺點是,編譯器無法偵測到模板缺失或模型物件錯誤。但是,它允許我們在生成模板類別之前編寫應用程式程式碼。
4.4. 使用有彈簧的搖桿
Rocker 並不直接支援 Spring,因此為了在控制器中使用它,我們需要編寫一個自訂的View實作:
public class RockerView implements View {
private final String viewName;
public RockerView(String viewName) {
this.viewName = viewName;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
BindableRockerModel template = Rocker.template(viewName);
for (Map.Entry<String, ?> entry : model.entrySet()) {
try {
template.bind(entry.getKey(), entry.getValue());
} catch (TemplateBindException e) {
// Ignore
}
}
RockerOutput output = template.render();
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.getWriter().write(output.toString());
}
}
這段程式碼會取得視圖名稱並嘗試綁定我們提供的模型映射中的所有內容。我們需要處理TemplateBindException異常,以防其中存在 Rocker 預期之外的條目。
然後我們就可以直接在控制器方法中使用它了:
@GetMapping("/rocker")
public ModelAndView get() {
ModelAndView modelAndView = new ModelAndView(new RockerView("RockerDemo.rocker.html"));
modelAndView.addObject("model", new RockerModel("Baeldung"));
return modelAndView;
}
然後,當使用此控制器時,它會將我們的 Rocker 模板渲染到回應中:
$ http localhost:8080/rocker
HTTP/1.1 200
Content-Length: 108
Content-Type: text/html;charset=UTF-8
<html>
<head>
</head>
<body>
<h1>Demo</h1>
<p>Hello Baeldung!</p>
</body>
</html>
5. JTE
接下來我們要介紹的函式庫是JTE 。它的運作方式與 Rocker 非常相似,都使用自訂模板語言和一個建置插件,將這些模板編譯成 Java 程式碼。不過,JTE 不使用反射,並且可以與 GraalVM 一起使用。
5.1. 依賴關係
要使用 JTE,我們需要在建置過程中進行設定。 JTE是一個自訂的 Maven 插件,可以自動將我們的範本轉換為 Java 程式碼。要使用它,我們需要在建置過程中包含最新版本:
<plugin>
<groupId>gg.jte</groupId>
<artifactId>jte-maven-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<sourceDirectory>${basedir}/src/main/resources/templates/jte</sourceDirectory>
<targetDirectory>${basedir}/target/jte</targetDirectory>
<contentType>Html</contentType>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
這段程式碼會在src/main/resources/templates/jte下搜尋模板文件,找出所有副檔名為.jte的文件。然後,它會將產生的 Java 程式碼寫入target/jte 。同時,它也會自動將此目錄新增為建置來源,以便將程式碼編譯到我們的應用程式中。
此外,我們還需要運行時依賴項才能使其正常運作:
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte</artifactId>
<version>3.2.1</version>
</dependency>
現在,我們已經準備好在我們的應用程式中使用它了。
5.2. 寫作模板
一切設定完畢後,我們就可以開始使用了。 JTE的工作方式與 Rocker 非常相似,我們的範本是主要輸入,但也可以引用其他 Java 類別來取得資料。
我們的模板是用一種自訂標記語言編寫的,這種語言看起來與 Rocker 使用的語言類似:
@import com.baeldung.templates.JteModel
@param JteModel model
<html>
<head>
</head>
<body>
<h1>Demo</h1>
<p>Hello ${model.name()}!</p>
</body>
</html>
與 Rocker 類似,我們導入一個必須已經存在的 Java 類,然後將其用作模板的參數。
模板編譯時類路徑上存在的任何類別都可以用作模型類:
public record JteModel(String name) { }
5.3.渲染模板
編譯完成後,我們就可以使用模板來產生輸出。與 Rocker 類似,在 JTE 中,我們不直接引用生成的類別。相反,我們使用TemplateEngine的一個實例來處理所有事情:
TemplateEngine templateEngine = TemplateEngine.createPrecompiled(ContentType.Html);
JteModel model = new JteModel("Baeldung");
StringOutput output = new StringOutput();
templateEngine.render("JteDemo.jte", model, output);
它會自動找到為我們的JteDemo.jte模板生成的類,並使用提供的模型物件來渲染它。
5.4. 與 Spring 一起使用
與 JStachio 一樣,JTE 提供了一個 Spring Boot starter,我們可以將其包含在我們的應用程式中以使用這些模板:
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte-spring-boot-starter-3</artifactId>
<version>3.2.1</version>
</dependency>
我們還需要在application.properties檔案中添加一些配置,以告訴它模板是如何建構的:
gg.jte.usePrecompiledTemplates=true
完成後,我們可以編寫一個控制器,該控制器返回視圖名稱並綁定我們的模型屬性:
@GetMapping("/jte")
public String view(Model model) {
model.addAttribute("model", new JteModel("Baeldung"));
return "JteDemo";
}
在這裡,我們只需要指定名稱“JteDemo”,因為 JTE 視圖解析器會自動為我們添加“.jte”後綴。
這樣就足以讓 JTE 使用提供的資料渲染對應的範本:
$ http localhost:8080/jte
HTTP/1.1 200
Content-Length: 103
Content-Type: text/html;charset=UTF-8
<html>
<head>
</head>
<body>
<h1>Demo</h1>
<p>Hello Baeldung!</p>
</body>
</html>
6. 曼特爾
我們最後一個函式庫是ManTL ,即 Manifold 範本庫。這是一個基於Manifold編譯器插件系統建構的模板庫。
6.1. 依賴關係
要使用 ManTL,我們需要在建置過程中對其進行配置。 ManTL是一個編譯器註解處理器,可以自動將我們的範本轉換為 Java 程式碼。要使用它,我們需要在建置過程中包含最新版本。
如果我們使用 Maven,我們可以將此依賴項新增到pom.xml檔案中:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-Xplugin:Manifold</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>systems.manifold</groupId>
<artifactId>manifold-templates</artifactId>
<version>2025.1.31</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
我們還需要引入一個運行時依賴項才能使用 ManTL 模板:
<dependency>
<groupId>systems.manifold</groupId>
<artifactId>manifold-templates-rt</artifactId>
<version>2025.1.31</version>
</dependency>
現在,我們已經準備好開始使用它了。
6.2. 寫作模板
設定完成後,我們就可以開始寫模板了。這些範本以.html.mtl檔案的形式編寫在src/main/resources目錄下,該目錄下的路徑是完全限定的類別名稱。例如, src/main/resources/templates/mantl.ManTLDemo.html.mtl等價於templates.mantl.ManTLDemo.
我們使用自訂標記語言編寫模板,這種語言與 Rocker 和 JTE 使用的語言類似:
<%@ import com.baeldung.templates.ManTLModel %>
<%@ params(ManTLModel model) %>
Hello ${model.name()}!
<%@ params() %>指令指示在渲染範本時需要傳遞給範本的參數,類似 Java 方法呼叫的參數。
與其他模板一樣,這些模板也可以引用我們編寫的 Java 類,這些類別提供資料供我們渲染:
public record ManTLModel(String name) { }
6.3.渲染模板
模板寫完後,我們就可以直接使用它來渲染輸出:
String output = templates.mantl.ManTLDemo.render(new ManTLModel("Baeldung"));
它以我們的模型物件作為參數,並產生完全渲染的輸出,供我們使用。
6.4. 與 Spring 一起使用
與 Rocker 一樣,ManTL 也不提供任何直接的 Spring 集成,因此我們需要編寫自己的View實作來使用它。然而,ManTL 範本的呼叫方式有所不同,所以我們不能簡單地提供一組模型物件。相反,我們將編寫一個視圖,使其接受一個返回字串的 lambda 表達式,然後我們可以在自己的程式碼中處理所有相關操作:
public class StringView implements View {
private final Supplier<String> output;
public StringView(Supplier<String> output) {
this.output = output;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.getWriter().write(output.get());
}
}
然後我們可以在控制器中使用它,在傳入的 lambda 表達式中呼叫我們的模板:
@GetMapping("/mantl")
public View get() {
return new StringView(() -> templates.mantl.ManTLDemo.render(new ManTLModel("Baeldung")));
}
現在,我們的控制器將渲染並返回我們的模板:
$ http localhost:8080/mantl
HTTP/1.1 200
Content-Length: 17
Content-Type: text/html;charset=UTF-8
Hello Baeldung!
7. 總結
在本文中,我們介紹了一些可以用作編譯時模板的不同函式庫。我們了解了它們是什麼,如何設定和使用它們,以及如何將它們與 Spring 控制器整合。下次當你的應用程式需要使用模板時,不妨試試其中的一個。
與往常一樣,本文中的所有範例都可以 在 GitHub 上找到。