3、使用Apache Tiles視圖定義布局
使用布局引擎,如Apache Tiles,定義适用于所有頁面的通用頁面布局。Spring MVC以視圖解析器的形式為Apache Tiles提供了支援,這個視圖解析器能夠将邏輯視圖名解析為Tile定義。
需導入Tiles相關的jar包:
tiles-api-3.0.8.jar
tiles-core-3.0.8.jar
tiles-jsp-3.0.8.jar
tiles-servlet-3.0.8.jar
tiles-template-3.0.8.jar
slf4j-api-1.7.25 (java.lang.ClassNotFoundException: org.slf4j.LoggerFactory)
commons-digester-2.1 (java.lang.ClassNotFoundException: org.apache.commons.digester.Rule)
commons-beanutils-1.9.3 (ClassNotFoundException: org.apache.commons.beanutils.MethodUtils)
tiles-request-api-1.0.7.(java.lang.NoClassDefFoundError: org/apache/tiles/request/render/Renderer)
tiles-request-servlet-1.0.7.(ClassNotFoundException: org.apache.tiles.request.servlet.ServletApplicationContext)
tiles-request-jsp-1.0.7 (ClassNotFoundException: org.apache.tiles.request.jsp.autotag.JspAutotagRuntime)
tiles-autotag-core-runtime-1.2 (ClassNotFoundException: org.apache.tiles.autotag.core.runtime.AutotagRuntime)
3.1 配置Tiles視圖解析器
為了在Spring中使用Tiles,需要配置幾個bean。我們需要一個TilesConfigurer bean,它會負責定位和加載Tile定義并協調生成Tiles。除此之外,還需要TilesViewResolver bean将邏輯視圖名稱解析為Tile定義。
這兩個元件又有兩種形式:針對Apache Tiles 2和Apache Tiles 3分别都有這麼兩個元件。這兩組Tiles元件之間最為明顯的差別在于包名。針對Apache Tiles 2的TilesConfigurer/TilesViewResolver位于org.springframework.web.servlet.view.tiles2包中,而針對Tiles 3的元件位于org.springframework.web.servlet.view.tiles3包中。對于該例子來講,我們使用的是Tiles 3。
WebConfig.java:
package spittr.config;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
@Configuration
@EnableWebMvc //啟用Spring Mvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* 配置Tiles視圖解析器
* @return
*/
@Bean
public ViewResolver viewResolver() {
return new TilesViewResolver();
}
// Tiles
@Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer tiles = new TilesConfigurer();
tiles.setDefinitions(new String[] {
"/WEB-INF/layout/tiles.xml",
});
//啟用重新整理功能
tiles.setCheckRefresh(true);
return tiles;
}
/**
* 配置靜态資源的處理
* 此配置要求DispatcherServlet将對靜态資源的請求轉發到Servlet容器中預設的Servlet上,
* 而非使用DispatcherServlet本身來處理此類請求
*/
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
// TODO Auto-generated method stub
//super.configureDefaultServletHandling(configurer);
configurer.enable();
}
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("/resources/messages");
messageSource.setCacheSeconds(10);
return messageSource;
}
}
當配置TilesConfigurer的時候,所要設定的最重要的屬性就是definitions。這個屬性接受一個String類型的數組,其中每個條目都指定一個Tile定義的XML檔案。對于Spittr應用來講,我們讓它在“/WEB-INF/layout/”目錄下查找tiles.xml。
還可以指定多個Tile定義檔案,甚至能夠在路徑位置上使用通配符,當然在上例中我們沒有使用該功能。例如,我們要求TilesConfigurer加載“/WEB-INF/”目錄下的所有名字為tiles.xml的檔案,那麼可以按照如下的方式設定definitions屬性:
tiles.setDefinitions(new String[] {
"/WEB-INF/**/tiles.xml",
});
接下來配置TilesViewResolver,可以看到,這是一個很基本的bean定義,沒有什麼要設定的屬性:
@Bean
public ViewResolver viewResolver() {
return new TilesViewResolver();
}
定義Tiles
Apache Tiles提供了一個文檔類型定義(document type definition,DTD),用來在XML檔案中指定Tile的定義。每個定義中需要包含一個<definition>元素,這個元素會有一個或多個<putattribute>元素。例如,如下的XML文檔為Spittr應用定義了幾個Tile。
程式清單6.2 WEB-INF/layout/tiles.xml:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<!-- 定義base Tile -->
<definition name="base" template="/WEB-INF/layout/page.jsp">
<put-attribute name="header" value="/WEB-INF/layout/header.jsp" />
<put-attribute name="footer" value="/WEB-INF/layout/footer.jsp" />
</definition>
<!-- 擴充base -->
<definition name="home" extends="base">
<put-attribute name="body" value="/WEB-INF/views/home.jsp" />
</definition>
<definition name="registerForm" extends="base">
<put-attribute name="body" value="/WEB-INF/views/registerForm.jsp" />
</definition>
<definition name="profile" extends="base">
<put-attribute name="body" value="/WEB-INF/views/profile.jsp" />
</definition>
<definition name="spittles" extends="base">
<put-attribute name="body" value="/WEB-INF/views/spittles.jsp" />
</definition>
<definition name="spittle" extends="base">
<put-attribute name="body" value="/WEB-INF/views/spittle.jsp" />
</definition>
</tiles-definitions>
每個<definition>元素都定義了一個Tile,它最終引用的是一個JSP模闆。在名為base的Tile中,模闆引用的是“/WEBINF/
layout/page.jsp”。某個Tile可能還會引用其他的JSP模闆,使這些JSP模闆嵌入到主模闆中。對于base Tile來講,它引用的是一個頭部JSP模闆和一個底部JSP模闆。
程式清單6.3 主布局模闆page.jsp:引用其他模闆來建立視圖
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="t" %>
<%@ page session="false" %>
<html>
<head>
<title>Spittr</title>
<link rel="stylesheet"
type="text/css"
href="<s:url value=" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" /resources/style.css" />" >
</head>
<body>
<!-- 插入頭部 -->
<div id="header">
<t:insertAttribute name="header" />
</div>
<!-- 插入主題内容 -->
<div id="content">
<t:insertAttribute name="body" />
</div>
<!-- 插入底部 -->
<div id="footer">
<t:insertAttribute name="footer" />
</div>
</body>
</html>
使用Tile标簽庫中的<t:insert Attribute> JSP标簽來插入其他的模闆,在這裡,用它來插入名為header、body和footer的模闆.
在base Tile定義中,header和footer屬性分别被設定為引用“/WEBINF/layout/ header. jsp”和“/WEB-INF/layout/footer.jsp”。但是body屬性呢?它是在哪裡設定的呢?
base Tile不會期望單獨使用,它會作為基礎定義,供其他的Tile定義擴充。在程式清單6.2的其餘内容中,可以看到其他的Tile定義都是擴充自base Tile。它意味着它們會繼承其header和footer屬性的設定(當然,Tile定義中也可以覆寫掉這些性),但是每一個都設定了body屬性,用來指定每個Tile特有的JSP模闆。
關注home Tile,它擴充了base。因為它擴充了base,是以它會繼承base中的模闆和所有的屬性。盡管home Tile定義相對來說很簡單,但是它實際上包含了如下的定義:
<definition name="home" extends="base">
<put-attribute name="header" value="/WEB-INF/layout/header.jsp" />
<put-attribute name="footer" value="/WEB-INF/layout/footer.jsp" />
<put-attribute name="body" value="/WEB-INF/views/home.jsp" />
</definition>
屬性所引用的每個模闆是很簡單的,如下是header.jsp模闆:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<a href="<s:url value=" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" /" />"><img
src="<s:url value="/resources" />/images/spitter_logo_50.png"
/></a>
footer.jsp模闆更為簡單:
Copyright © Craig Walls
每個擴充自base的Tile都定義了自己的主體區模闆,是以每個都會與其他的有所差別。
home.jsp:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<h1>Welcome to Spitter</h1>
<a href="<c:url value=" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" /spittles" />">Spittles</a> |
<a href="<c:url value=" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" /spitter/register" />">Register</a>
這裡的關鍵點在于通用的元素放到了page.jsp、header.jsp以及footer.jsp中,其他的Tile模闆中不再包含這部分内容。這使得它們能夠跨頁面重用,這些元素的維護也得以簡化。
運作結果:
4、使用Thymeleaf
多年來,在Java應用中,有多個項目試圖挑戰JSP在視圖領域的統治 性地位。最新的挑戰者是Thymeleaf,它展現了一些切實的承諾,是 一項很令人興奮的可選方案。Thymeleaf模闆是原生的,不依賴于标簽庫。它能在接受原始HTML的地方進行編輯和渲染。因為它沒有與 Servlet規範耦合,是以Thymeleaf模闆能夠進入JSP所無法涉足的領域。
4.1 配置Thymeleaf視圖解析器
為了要在Spring中使用Thymeleaf,我們需要配置三個啟用Thymeleaf與 Spring內建的bean:
(1)ThymeleafViewResolver:将邏輯視圖名稱解析為Thymeleaf 模闆視圖;
(2)SpringTemplateEngine:處理模闆并渲染結果;
(3)TemplateResolver:加載Thymeleaf模闆。
需要引入以下jar包:
thymeleaf-3.0.9
thymeleaf-spring4-3.0.9
attoparser-2.0.4
unbescape-1.1.5
清單 WebConfig.java:
package spittr.config;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
@Configuration
@EnableWebMvc //啟用Spring Mvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* 配置Thymeleaf視圖解析器
* @return
*/
@Bean
public ThymeleafViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
/**
* 模闆引擎,處理模闆并渲染結果
* @param templateResolver
* @return
*/
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
/**
* 模闆解析器
* @return
*/
@Bean
public ITemplateResolver templateResolver() {
// SpringResourceTemplateResolver自動與Spring自己內建, 資源解決基礎設施, 強烈推薦。
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver ();
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
//要解析的模闆會渲染成HTML5輸出
templateResolver.setTemplateMode("HTML5");
// 預設情況下, 模闆緩存為true。如果您想要設定為false。模闆在修改時自動更新。
templateResolver.setCacheable(true);
return templateResolver;
}
/**
* 配置靜态資源的處理
* 此配置要求DispatcherServlet将對靜态資源的請求轉發到Servlet容器中預設的Servlet上,
* 而非使用DispatcherServlet本身來處理此類請求
*/
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
// TODO Auto-generated method stub
//super.configureDefaultServletHandling(configurer);
configurer.enable();
}
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("/resources/messages");
messageSource.setCacheSeconds(10);
return messageSource;
}
}
ThymeleafViewResolver是Spring MVC中ViewResolver的一個實作類。像其他的視圖解析器一樣,它會接受一個邏輯視圖名稱,并 将其解析為視圖。不過在該場景下,視圖會是一個Thymeleaf模闆。
需要注意的是ThymeleafViewResolver bean中注入了一個對 SpringTemplate Engine bean的引用。SpringTemplateEngine會在Spring中啟用Thymeleaf引擎,用來解析模闆,并基于這些模闆渲染結果。可以看到,我們為其注入了一個TemplateResolver bean的引用。
TemplateResolver會最終定位和查找模闆。與之前配 置InternalResource-ViewResolver類似,它使用了prefix和suffix屬性。字首和字尾将會與邏輯視圖名組合使用,進而定位Thymeleaf引擎。它的templateMode屬性被設定成了HTML 5,這表明我們預期要解析的模闆會渲染成HTML 5輸出。
4.2 定義Thymeleaf模闆
Thymeleaf在很大程度上就是HTML檔案,與JSP不同,它沒有什麼特 殊的标簽或标簽庫。Thymeleaf之是以能夠發揮作用,是因為它通過 自定義的命名空間,為标準的HTML标簽集合添加Thymeleaf屬性。如下的程式清單展現了home.html,也就是使用Thymeleaf命名空間的首頁模闆。
清單 home.html:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spitter</title>
<link rel="stylesheet"
type="text/css"
th:href="{/resources/style.css}" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ></link>
</head>
<body>
<div id="header" th:include="page :: header"></div>
<div id="content">
<h1>Welcome to Spitter</h1>
<a th:href="@{/spittles}" target="_blank" rel="external nofollow" >Spittles</a> |
<a th:href="@{/spitter/register}" target="_blank" rel="external nofollow" >Register</a>
<br/>
View: <span th:text="${view}">unknown</span>
</div>
<div id="footer" th:include="page :: copy"></div>
</body>
</html>
首頁模闆相對來講很簡單,隻使用了th:href屬性。這個屬性與對 應的原生HTML屬性很類似,也就是href屬性,并且可以按照相同的方式來使用。th:href屬性的特殊之處在于它的值中可以包含Thymeleaf表達式,用來計算動态的值。它會渲染成一個标準的href 屬性,其中會包含在渲染時動态建立得到的值。
“@{}”表達式用來計算相對于 URL的路徑(就像在JSP頁面中,我們可能會使用的JSTL <c:url>标 簽或Spring<s:url>标簽類似)。
借助Thymeleaf實作表單綁定
表單綁定是Spring MVC的一項重要特性。它能夠将表單送出的資料填充到指令對象中,并将其傳遞給控制器,而在展現表單的時候,表單 中也會填充指令對象中的值。如果沒有表單綁定功能的話,我們需要確定HTML表單域要映射後端指令對象中的屬性,并且在校驗失敗後 展現表單的時候,還要負責確定輸入域中值要設定為指令對象的屬 性。
作為闡述的樣例,請參考如下的Thymeleaf模闆片段,它會渲染First Name輸入域:
<label th:class="${#fields.hasErrors('firstName')}? 'error'">First Name</label>:
<input type="text" th:field="*{firstName}"
th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>
在這裡,我們不再使用Spring JSP标簽中的cssClassName屬性,而是在标準的HTML标簽上使用th:class屬性。th:class屬性會渲染為一個class屬性,它的值是根據給定的表達式計算得到的。在上面的這兩個th:class屬性中,它會直接檢查firstName域有沒有校驗錯誤。如果有的話,class屬性在渲染時的值為error。如果這 個域沒有錯誤的話,将不會渲染class屬性。
<input>标簽使用了th:field屬性,用來引用後端對象的 firstName域。
清單 /spittr/WebRoot/WEB-INF/views/registerForm.html:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spitter</title>
<link rel="stylesheet" type="text/css"
th:href="{/resources/style.css}" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" ></link>
</head>
<body>
<div id="header" th:include="page :: header"></div>
<div id="content">
<h1>Register</h1>
<form method="POST" th:object="${spitter}">
<div class="errors" th:if="${#fields.hasErrors('*')}">
<ul>
<li th:each="err : ${#fields.errors('*')}"
th:text="${err}">Input is incorrect</li>
</ul>
</div>
<label th:class="${#fields.hasErrors('firstName')}? 'error'">First Name</label>:
<input type="text" th:field="*{firstName}"
th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('lastName')}? 'error'">Last Name</label>:
<input type="text" th:field="*{lastName}"
th:class="${#fields.hasErrors('lastName')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('email')}? 'error'">Email</label>:
<input type="text" th:field="*{email}"
th:class="${#fields.hasErrors('email')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('username')}? 'error'">Username</label>:
<input type="text" th:field="*{username}"
th:class="${#fields.hasErrors('username')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('password')}? 'error'">Password</label>:
<input type="password" th:field="*{password}"
th:class="${#fields.hasErrors('password')}? 'error'" /><br/>
<input type="submit" value="Register" />
</form>
</div>
<div id="footer" th:include="page :: copy"></div>
</body>
</html>
運作結果: