2.2.3 Spring 通用的標簽庫
除了表單綁定標簽庫之外,Spring還提供了更為通用的JSP標簽庫。要使用它,必須在頁面上對其進行聲明:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
| JSP標簽 | 描述 |
|---|---|
<s:bind> |
將綁定屬性的狀態(tài)導出到一個名為status的頁面作用域?qū)傩灾?,與<s:path>組合使用獲取綁定屬性的值 |
<s:escapeBody> |
將標簽體中的內(nèi)容進行HTML和/或JS轉(zhuǎn)義 |
<s:hasBindErrors> |
根據(jù)指定模型對象(在請求屬性中)是否有綁定錯誤,有條件地渲染內(nèi)容 |
<s:htmlEscape> |
為當前頁面設置默認的HTML轉(zhuǎn)義值 |
<s:message> |
根據(jù)給定的編碼獲取信息,然后要么進行渲染(默認行為),要么將其設置為頁面作用域、請求作用域、會話作用域或應用作用域的變量(通過使用var和scope屬性實現(xiàn)) |
<s:nestedPath> |
設置嵌入式的path,用于<s:bind>之中 |
<s:theme> |
根據(jù)給定的編碼獲取主題信息,然后要么進行渲染(默認行為),要么將其設置為頁面作用域、請求作用域、會話作用域或應用作用域的變量(通過使用var和scope屬性實現(xiàn)) |
<s:transform> |
使用命令對象的屬性編輯器轉(zhuǎn)換命令對象中不包含的屬性 |
<s:url> |
創(chuàng)建相對于上下文的URL,支持URI模版變量以及HTML/XML/JS轉(zhuǎn)義,可以渲染URL(默認行為),也可以將其設置為頁面作用域、請求作用域、會話作用域或應用作用域的變量(通過使用var和scope屬性實現(xiàn)) |
<s:eval> |
計算符合Spring表達式語言(SpEL)語法的某個表達式的值,然后然后要么進行渲染(默認行為),要么將其設置為頁面作用域、請求作用域、會話作用域或應用作用域的變量(通過使用var和scope屬性實現(xiàn)) |
2.2.4 展現(xiàn)國際化信息
在進行國際化中,對于渲染文本來說,<s:message>是很好的方案:
<h1><s:message code="spittr.welcome" /></h1>
按照這里的方式,<s:message>將會根據(jù)key為spittr.welcome的信息來渲染文本。因此,如果希望此標簽能完成任務,就需要配置一個這樣的信息源。Spring有多個信息源的類,它們都實現(xiàn)了MessageSource接口。比較常用的是ResourceBundleMessageSource。它會從一個屬性文件中加載信息,這個屬性文件的名稱是根據(jù)基礎名稱衍生而來的:
@Bean
public MessageSource messageSource(){
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("message");
return messageSource;
}
在上面代碼中,核心在于設置basename屬性,將其設置為message后,ResourceBundleMessageSource就會試圖在根路徑的屬性文件中解析信息,這些屬性文件的名稱是根據(jù)這個基礎名衍生得到的(message.properties)。
另外可選方案是使ReloadableResourceBundleMessageSource,使用和工作方式和ResourceBundleMessageSource非常類似,但是它能夠重新加載信息屬性,而不必重新編譯或重啟應用。
@Bean
public MessageSource messageSource(){
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("file:///etc/spittr/message");
messageSource.setCacheSeconds(10);
return messageSource;
}
說明:這里basename設置為在應用外部查找,還可以設置為在類路徑下(以“classpath:”作為前綴)、文件系統(tǒng)中(以“file:”作為前綴)或Web應用的根路徑下(沒有前綴)查找屬性。
2.2.5 創(chuàng)建URL
<s:url>是一個很小的標簽,其主要任務就是創(chuàng)建URL,然后將其賦值給一個變量或者渲染到響應中。它是JSTL中<c:url>標簽的替代者,但是有幾項特殊技巧。
按照其最簡單的形式,<s:url>會接受一個相對于Servlet上下文的URL,并在渲染的時候,預先添加上Servlet上下文路徑。
<a href="<s:url href="/spitter/register" />">Register</a>
如果應用的Servlet上下文名為spittr,那么響應中將渲染為如下的HTML:
<a href="/spittr/spitter/register">Register</a>
還可以使用<s:url>創(chuàng)建URL,并將其賦值給一個變量供模版在稍后使用:
<s:url href="/spitter/register" var="registerUrl" />
<a href="${registerUrl}">Register</a>
默認情況,URL是在頁面作用域內(nèi)創(chuàng)建的。但是通過設置scope屬性,我們可以讓<s:url>在應用作用域內(nèi)、會話作用域內(nèi)或請求作用域內(nèi)創(chuàng)建URL:
<s:url href="/spitter/register" var="registerUrl" scope="request" />
如果希望在URL上添加參數(shù)的話,那么你可以使用<s:param>標簽。
<s:url href="/spittles" var="spittlesUrl">
<s:param name="max" value="60">
<s:param name="count" value="20">
</s:url>
上面講到的功能,在JSTL <c:url>中也有,但是如果向要創(chuàng)建帶有路徑參數(shù)的URL,則需要使用<s:url>了。
<s:url href="/spittles/{username}" var="spittlesUrl">
<s:param name="username" value="jack">
</s:url>
當href屬性中的占位符匹配<s:param>中所指定的參數(shù)時,這個參數(shù)將會插入到占位符的位置中。如果<s:param>參數(shù)無法匹配href中的任何占位符,那么這個參數(shù)(此處是username)將會作為查詢參數(shù)。
<s:url>標簽還可以解決URL的轉(zhuǎn)移需求,如過希望將渲染得到的URL內(nèi)容展現(xiàn)在Web頁面上(而不是作為超鏈接),可以這樣:
<s:url href="/spittles" htmlEscape="true">
<s:param name="max" value="60">
<s:param name="count" value="20">
</s:url>
所渲染的結(jié)果如下:
/spitter/spittles?max=60&count=20
當然如果想在JS中使用URL則需要設置javascriptEscape屬性:
<s:url href="/spittles" javascriptEscape="true" var="spittlesJSUrl">
<s:param name="max" value="60">
<s:param name="count" value="20">
</s:url>
使用如下:
<script>
var spittlesUrl = "{spittlesJSUrl}"
</script>
渲染結(jié)果為:
<script>
var spittlesUrl = "\/spitter\/spittles?max=60&count=20"
</script>
2.2.6 轉(zhuǎn)義內(nèi)容
<s:escapeBody>標簽是一個通用的轉(zhuǎn)義標簽。它會渲染標簽中內(nèi)嵌的內(nèi)容,并且在必要的時候進行轉(zhuǎn)義。如想在頁面展現(xiàn)一個HTML代碼片段:
<s:escapeBody htmlEscape="true">
<h1>Hello</h1>
</s:escapeBody>
它將會渲染成如下內(nèi)容:
<h1>Hello</h1>
在瀏覽器中顯示的時候就會自動轉(zhuǎn)義為:
<h1>Hello</h1>
當然還可以設置javascriptEscape屬性對JS進行轉(zhuǎn)義,但是此標簽不能將內(nèi)容設置為變量。
三、使用 Apache Tiles 視圖定義布局
如果我們想為應用中的所有頁面定義一個通用的頭部和底部。最原始的方式就是查找每個JSP,并為其添加頭部和底部的HTML。但是這種方式的擴展性并不好。更好的方式是使用布局引擎,如Apache Tiles,定義適用于所有頁面的通用頁面布局。
3.1 配合Tiles視圖解析器
為了在Spring中使用Tiles,需要配置幾個bean。需要一個TilesConfigurer bean,它會負責定位和加載Tile定義并協(xié)調(diào)生成Tiles。除此之外,還需要TilesViewResolver bean將邏輯視圖名稱解析為Tile定義。
這兩個組件又有兩種形式:針對Apache Tiles 2(位于org.springframework.web.servlet.view.tiles2)和Apache Tiles 3(位于org.springframework.web.servlet.view.tiles3)分別都有這么兩個組件。
首先,配置TilesConfigurer來解析Tile定義。
@Bean
public TilesConfigurer tilesConfigurer(){
TilesConfigurer tiles = new TilesConfigurer();
tiles.setDefinitions(new String[] {"/WEB-INF/layout/tiles.xml"});
tiles.setCheckRefresh(true);
return tiles;
}
說明:配置TilesConfigurer的時候,所要設置的最重要的屬性就是definitions。接受一個String類型的數(shù)組,其中每個條目都指定一個Tile定義的XML文件。這里讓它在"/WEB-INF/layout/"下查找tiles.xml文件。如果我們想讓其加載"/WEB-INF/"目錄下的所有名為tiles.xml的文件則可以這樣配置路徑:/WEB-INF/**/tiles.xml。
接下來,讓我們配置TilesViewResolver,可以看到,這是一個很基本的bean定義,沒有什么要設置的屬性:
@Bean
public ViewResolver viewResolver(){
return new TilesViewResolver();
}
當然也可以使用XML的方式進行配置:
<bean id="tilesCOnfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/layout/tiles.xml</value>
<value>/WEB-INF/**/tiles.xml</value>
</list>
</property>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.tiles3.TilesViewResolver" />
說明:TilesConfigurer會加載Tile定義并與Apache Tiles協(xié)作,而TilesViewResolver會將邏輯視圖名稱解析為引用Tile定義的視圖。
3.1.1 定義Tiles
下面看如何定義Tiles文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!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>
<definition name="home" extends="base"><!--擴展base Tile-->
<put-attribute name="body" value="/WEB-INF/views/home.jsp" />
</definition>
<definition name="registerForm" extends="base"><!--擴展base Tile-->
<put-attribute name="body" value="/WEB-INF/views/registerForm.jsp" />
</definition>
<definition name="profile" extends="base"><!--擴展base Tile-->
<put-attribute name="body" value="/WEB-INF/views/profile.jsp" />
</definition>
<definition name="spittles" extends="base"><!--擴展base Tile-->
<put-attribute name="body" value="/WEB-INF/views/spittles.jsp" />
</definition>
<definition name="spittle" extends="base"><!--擴展base Tile-->
<put-attribute name="body" value="/WEB-INF/views/spittle.jsp" />
</definition>
</tiles-definitions>
說明:每個<definition>元素都定義了一個Tile,最終引用的是一個JSP模版??梢钥吹?code>base Tile中設置了基本的模版,及頂部和底部。而后面的Tile都是用來擴展base Tile的,后面的Tile都將作為body插入到整個頁面,和頂部、底部一起組成一個完整的頁面。我們看base Tile所引用的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="/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會繼承base Tile的相關屬性,如home Tile實際上包含了如下定義:
<definition name="home" 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" />
<put-attribute name="body" value="/WEB-INF/views/home.jsp" />
</definition>
為了完整的了解home Tile,如下展現(xiàn)了home.jsp:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<h1>Welcome to Spittr</h1>
<a href="<c:url value="spittles" />">Spittles</a>
<a href="<c:url value="spittler/register" />">Register</a>
四、使用Thymeleaf
JSP規(guī)范與Servlet規(guī)范緊密耦合,這意味著它只能用在基于Servlet的Web應用之中。JSP模版不能作為通用的模版(如格式化Email),也不能用于非Servlet的Web應用。
4.1 配置Thymeleaf視圖解析器
為了要在Spring中使用Thymeleaf,需要配置三個啟用Thymeleaf與Spring集成的bean:
-
ThymeleafViewResolver:將邏輯試圖名稱為解析為Thymeleaf模版視圖; -
SpringTemplateEngine:處理模版并渲染結(jié)果; -
TemplateResolver:加載Thymeleaf模版。
如下為聲明這些bean的Java配置(在WebConfig.java中):
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
@Bean
public TemplateEngine templateEngine(TemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
@Bean
public TemplateResolver templateResolver() {
TemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
return templateResolver;
}
當然也可以使用XML方式進行配置:
<bean id="viewResolver" class="org.thymeleaf.spring3.view.ThymeleafViewResolver"
p:templateEngine-ref="templateEngine" />
<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine"
p:templateEngine-ref="templateResolver" />
<bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver"
p:prefix="/WEB-INF/templates"
p:suffix=".html"
p:templateMode="HTML5"/>
說明:ThymeleafViewResolver是Spring MVC中ViewResolver的一個實現(xiàn),會將一個邏輯視圖名解析為一個Thymeleaf模版。在ThymeleafViewResolver bean中注入了一個對SpringTemplateEngine bean的引用。SpringTemplateEngine會在Spring中啟用Thymeleaf引擎,用來解析模版,并基于這些模版渲染結(jié)果??梢钥吹剑覀?yōu)槠渥⑷肓艘粋€TemplateResolver bean的引用。TemplateResolver會最終定位和查找模版。
4.2 定義Thymeleaf模版
Thymeleaf在很大程度上就是HTML文件,與JSP不同,它沒有什么特殊的標簽或標簽庫。它通過自定義的命名空間,為標準HTML標簽集合添加Thymeleaf屬性。
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"><!--聲明Thymeleaf命名空間-->
<head>
<title>Spitter</title>
<link rel="stylesheet" type="text/css"
th:href="@{/resources/style.css}"></link><!--到樣式表的th:href鏈接-->
</head>
<body>
<div id="header" th:include="page :: header"></div>
<div id="content">
<h1>Welcome to Spitter</h1>
<!--到頁面的th:href鏈接-->
<a th:href="@{/spittles}">Spittles</a> |
<a th:href="@{/spitter/register}">Register</a>
</body>
</html>
說明:th:href屬性的特殊之處在于它的值中可以包含Thymeleaf表達式,用來計算動態(tài)的值。Thymeleaf的價值在于它與純HTML模版非常接近。唯一的區(qū)別就是使用了th:href屬性。這意味著Thymeleaf模版與JSP不同,它能夠按照原始的方式進行編輯甚至渲染,而不必經(jīng)過任何類型的處理器。
4.2.1 借助Thymeleaf實現(xiàn)表單綁定
下面直接通過表單頁面registerForm.html進行說明:
<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>
說明:這里我們使用的是Thymeleaf的方言。th:class屬性會渲染為一個class屬性,它的值是根據(jù)給定的表達式計算得到的。如果有有錯誤,class在渲染時的值為error,如果這個域沒有錯誤,將不會渲染class屬性。th:field屬性用來引用后端對象的相關域(如spitter的firstName屬性),其中星號表示為所有表單對象(此處為spitter)定義后端對象。使用th:if屬性來檢查是否有校驗錯誤,如果有,會渲染<div>,否則不渲染。<li>標簽上的th:each屬性將會通知Thymeleaf為每項錯誤都渲染一個<li>,在每次迭代中會將當前錯誤設置到一個名為err的變量中。
“${}”和“*{}”有什么區(qū)別:前者是變量表達式。一般來講,會是OGNL表達式,在使用Spring時是SpEL表達式。在${spitter}例子中,會解析key為spitter的model屬性。而后者是選擇表達式。變量表達式是基于整個SpEL上下文計算的,而選擇表達式是基于一個選中對象計算的。本例中選擇的是Spitter對象。