JSP 和 JSTL

Spring 框架內建整合了 Spring MVC 與 JSP 和 JSTL 的使用。

檢視解析器

當使用 JSP 開發時,您通常會宣告一個 InternalResourceViewResolver Bean。

InternalResourceViewResolver 可用於分派到任何 Servlet 資源,特別是 JSP。作為最佳實務,我們強烈建議將您的 JSP 檔案放在 'WEB-INF' 目錄下的目錄中,這樣用戶端就無法直接存取。

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
	<property name="prefix" value="/WEB-INF/jsp/"/>
	<property name="suffix" value=".jsp"/>
</bean>

JSP 與 JSTL

當使用 JSP 標準標籤庫 (JSTL) 時,您必須使用特殊的檢視類別 JstlView,因為 JSTL 在國際化 (I18N) 功能等運作之前需要一些準備工作。

Spring 的 JSP 標籤庫

Spring 提供請求參數到命令物件的資料繫結,如先前章節所述。為了促進 JSP 頁面與這些資料繫結功能結合的開發,Spring 提供了一些標籤,使事情變得更加容易。所有 Spring 標籤都具有 HTML 跳脫字元功能,可以啟用或停用字元的跳脫。

spring.tld 標籤庫描述符 (TLD) 包含在 spring-webmvc.jar 中。有關個別標籤的完整參考,請瀏覽 API 參考 或查看標籤庫描述。

Spring 的表單標籤庫

從 2.0 版開始,Spring 提供了一組全面的資料繫結感知標籤,用於在使用 JSP 和 Spring Web MVC 時處理表單元素。每個標籤都為其對應的 HTML 標籤副本的屬性集提供支援,使標籤使用起來熟悉且直觀。標籤產生的 HTML 符合 HTML 4.01/XHTML 1.0 標準。

與其他表單/輸入標籤庫不同,Spring 的表單標籤庫與 Spring Web MVC 整合,使標籤可以存取您的控制器處理的命令物件和參考資料。正如我們在以下範例中所示,表單標籤使 JSP 更容易開發、閱讀和維護。

我們將逐步介紹表單標籤,並查看每個標籤如何使用的範例。我們包含了產生的 HTML 片段,其中某些標籤需要進一步的註解。

設定

表單標籤庫捆綁在 spring-webmvc.jar 中。庫描述符稱為 spring-form.tld

若要使用此庫中的標籤,請將以下指示詞新增至 JSP 頁面的頂端

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

其中 form 是您要用於此庫中標籤的標籤名稱前綴。

表單標籤

此標籤呈現 HTML 'form' 元素,並公開內部標籤的繫結路徑以進行繫結。它將命令物件放在 PageContext 中,以便內部標籤可以存取命令物件。此庫中的所有其他標籤都是 form 標籤的巢狀標籤。

假設我們有一個名為 User 的網域物件。它是一個 JavaBean,具有 firstNamelastName 等屬性。我們可以將其用作表單控制器的表單後端物件,該控制器傳回 form.jsp。以下範例顯示 form.jsp 可能的外觀

<form:form>
	<table>
		<tr>
			<td>First Name:</td>
			<td><form:input path="firstName"/></td>
		</tr>
		<tr>
			<td>Last Name:</td>
			<td><form:input path="lastName"/></td>
		</tr>
		<tr>
			<td colspan="2">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form:form>

firstNamelastName 值是從頁面控制器放在 PageContext 中的命令物件中檢索的。繼續閱讀以查看內部標籤如何與 form 標籤一起使用的更複雜範例。

以下清單顯示產生的 HTML,其外觀類似於標準表單

<form method="POST">
	<table>
		<tr>
			<td>First Name:</td>
			<td><input name="firstName" type="text" value="Harry"/></td>
		</tr>
		<tr>
			<td>Last Name:</td>
			<td><input name="lastName" type="text" value="Potter"/></td>
		</tr>
		<tr>
			<td colspan="2">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form>

前面的 JSP 假設表單後端物件的變數名稱為 command。如果您已將表單後端物件放在模型中的另一個名稱下(絕對是最佳實務),則可以將表單繫結到具名變數,如下列範例所示

<form:form modelAttribute="user">
	<table>
		<tr>
			<td>First Name:</td>
			<td><form:input path="firstName"/></td>
		</tr>
		<tr>
			<td>Last Name:</td>
			<td><form:input path="lastName"/></td>
		</tr>
		<tr>
			<td colspan="2">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form:form>

input 標籤

此標籤使用繫結值和預設的 type='text' 呈現 HTML input 元素。有關此標籤的範例,請參閱 表單標籤。您也可以使用 HTML5 特定的類型,例如 emailteldate 和其他類型。

checkbox 標籤

此標籤呈現 HTML input 標籤,其中 type 設定為 checkbox

假設我們的 User 有偏好設定,例如電子報訂閱和興趣清單。以下範例顯示 Preferences 類別

  • Java

  • Kotlin

public class Preferences {

	private boolean receiveNewsletter;
	private String[] interests;
	private String favouriteWord;

	public boolean isReceiveNewsletter() {
		return receiveNewsletter;
	}

	public void setReceiveNewsletter(boolean receiveNewsletter) {
		this.receiveNewsletter = receiveNewsletter;
	}

	public String[] getInterests() {
		return interests;
	}

	public void setInterests(String[] interests) {
		this.interests = interests;
	}

	public String getFavouriteWord() {
		return favouriteWord;
	}

	public void setFavouriteWord(String favouriteWord) {
		this.favouriteWord = favouriteWord;
	}
}
class Preferences(
		var receiveNewsletter: Boolean,
		var interests: StringArray,
		var favouriteWord: String
)

對應的 form.jsp 可能類似於以下內容

<form:form>
	<table>
		<tr>
			<td>Subscribe to newsletter?:</td>
			<%-- Approach 1: Property is of type java.lang.Boolean --%>
			<td><form:checkbox path="preferences.receiveNewsletter"/></td>
		</tr>

		<tr>
			<td>Interests:</td>
			<%-- Approach 2: Property is of an array or of type java.util.Collection --%>
			<td>
				Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
				Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
				Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
			</td>
		</tr>

		<tr>
			<td>Favourite Word:</td>
			<%-- Approach 3: Property is of type java.lang.Object --%>
			<td>
				Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>
			</td>
		</tr>
	</table>
</form:form>

checkbox 標籤有三種方法,應滿足您的所有核取方塊需求。

  • 方法一:當繫結值為 java.lang.Boolean 類型時,如果繫結值為 true,則 input(checkbox) 標記為 checkedvalue 屬性對應於 setValue(Object) 值屬性的解析值。

  • 方法二:當繫結值為 arrayjava.util.Collection 類型時,如果已設定的 setValue(Object) 值存在於繫結的 Collection 中,則 input(checkbox) 標記為 checked

  • 方法三:對於任何其他繫結值類型,如果已設定的 setValue(Object) 等於繫結值,則 input(checkbox) 標記為 checked

請注意,無論使用哪種方法,都會產生相同的 HTML 結構。以下 HTML 片段定義了一些核取方塊

<tr>
	<td>Interests:</td>
	<td>
		Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
		<input type="hidden" value="1" name="_preferences.interests"/>
		Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
		<input type="hidden" value="1" name="_preferences.interests"/>
		Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
		<input type="hidden" value="1" name="_preferences.interests"/>
	</td>
</tr>

您可能不希望在每個核取方塊後看到額外的隱藏欄位。當 HTML 頁面中的核取方塊未勾選時,其值不會作為 HTTP 請求參數的一部分傳送到伺服器(一旦提交表單),因此我們需要一種解決方法來解決 HTML 中的這種怪癖,以使 Spring 表單資料繫結正常運作。checkbox 標籤遵循現有的 Spring 慣例,即為每個核取方塊包含一個以下底線 (_) 為前綴的隱藏參數。透過這樣做,您實際上是在告訴 Spring:「核取方塊在表單中可見,我希望我的物件(表單資料繫結到的物件)反映核取方塊的狀態,無論如何。」

checkboxes 標籤

此標籤呈現多個 HTML input 標籤,其中 type 設定為 checkbox

本節建立在先前 checkbox 標籤章節的範例之上。有時,您可能不想在 JSP 頁面中列出所有可能的興趣。您寧願在執行階段提供可用選項的清單,並將其傳遞到標籤中。這就是 checkboxes 標籤的目的。您可以在 items 屬性中傳遞一個包含可用選項的 ArrayListMap。通常,繫結屬性是一個集合,以便它可以保存使用者選取的多個值。以下範例顯示使用此標籤的 JSP

<form:form>
	<table>
		<tr>
			<td>Interests:</td>
			<td>
				<%-- Property is of an array or of type java.util.Collection --%>
				<form:checkboxes path="preferences.interests" items="${interestList}"/>
			</td>
		</tr>
	</table>
</form:form>

此範例假設 interestList 是一個 List,作為模型屬性提供,其中包含要從中選取的值字串。如果您使用 Map,則 Map 項目鍵用作值,Map 項目的值用作要顯示的標籤。您也可以使用自訂物件,您可以使用 itemValue 提供值的屬性名稱,並使用 itemLabel 提供標籤。

radiobutton 標籤

此標籤呈現 HTML input 元素,其中 type 設定為 radio

典型的用法模式涉及多個標籤實例,這些實例繫結到相同的屬性,但具有不同的值,如下列範例所示

<tr>
	<td>Sex:</td>
	<td>
		Male: <form:radiobutton path="sex" value="M"/> <br/>
		Female: <form:radiobutton path="sex" value="F"/>
	</td>
</tr>

radiobuttons 標籤

此標籤呈現多個 HTML input 元素,其中 type 設定為 radio

checkboxes 標籤 類似,您可能想要將可用選項作為執行階段變數傳遞。對於此用法,您可以使用 radiobuttons 標籤。您可以在 items 屬性中傳遞一個包含可用選項的 ArrayListMap。如果您使用 Map,則 Map 項目鍵用作值,Map 項目的值用作要顯示的標籤。您也可以使用自訂物件,您可以使用 itemValue 提供值的屬性名稱,並使用 itemLabel 提供標籤,如下列範例所示

<tr>
	<td>Sex:</td>
	<td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>

password 標籤

此標籤呈現 HTML input 標籤,其中 type 設定為 password,並帶有繫結值。

<tr>
	<td>Password:</td>
	<td>
		<form:password path="password"/>
	</td>
</tr>

請注意,預設情況下,密碼值不會顯示。如果您確實希望顯示密碼值,您可以將 showPassword 屬性的值設定為 true,如下列範例所示

<tr>
	<td>Password:</td>
	<td>
		<form:password path="password" value="^76525bvHGq" showPassword="true"/>
	</td>
</tr>

select 標籤

此標籤呈現 HTML 'select' 元素。它支援資料繫結到選取的選項,以及巢狀 optionoptions 標籤的使用。

假設 User 有一個技能清單。對應的 HTML 可能如下所示

<tr>
	<td>Skills:</td>
	<td><form:select path="skills" items="${skills}"/></td>
</tr>

如果 User 的技能是草藥學,則「技能」列的 HTML 原始碼可能如下所示

<tr>
	<td>Skills:</td>
	<td>
		<select name="skills" multiple="true">
			<option value="Potions">Potions</option>
			<option value="Herbology" selected="selected">Herbology</option>
			<option value="Quidditch">Quidditch</option>
		</select>
	</td>
</tr>

option 標籤

此標籤呈現 HTML option 元素。它根據繫結值設定 selected。以下 HTML 顯示其典型輸出

<tr>
	<td>House:</td>
	<td>
		<form:select path="house">
			<form:option value="Gryffindor"/>
			<form:option value="Hufflepuff"/>
			<form:option value="Ravenclaw"/>
			<form:option value="Slytherin"/>
		</form:select>
	</td>
</tr>

如果 User 的學院是葛萊分多,則「學院」列的 HTML 原始碼將如下所示

<tr>
	<td>House:</td>
	<td>
		<select name="house">
			<option value="Gryffindor" selected="selected">Gryffindor</option> (1)
			<option value="Hufflepuff">Hufflepuff</option>
			<option value="Ravenclaw">Ravenclaw</option>
			<option value="Slytherin">Slytherin</option>
		</select>
	</td>
</tr>
1 請注意新增的 selected 屬性。

options 標籤

此標籤呈現 HTML option 元素的清單。它根據繫結值設定 selected 屬性。以下 HTML 顯示其典型輸出

<tr>
	<td>Country:</td>
	<td>
		<form:select path="country">
			<form:option value="-" label="--Please Select"/>
			<form:options items="${countryList}" itemValue="code" itemLabel="name"/>
		</form:select>
	</td>
</tr>

如果 User 居住在英國,則「國家/地區」列的 HTML 原始碼將如下所示

<tr>
	<td>Country:</td>
	<td>
		<select name="country">
			<option value="-">--Please Select</option>
			<option value="AT">Austria</option>
			<option value="UK" selected="selected">United Kingdom</option> (1)
			<option value="US">United States</option>
		</select>
	</td>
</tr>
1 請注意新增的 selected 屬性。

如前一個範例所示,option 標籤與 options 標籤的組合使用會產生相同的標準 HTML,但可讓您在 JSP 中明確指定一個僅供顯示的值(它所屬的位置),例如範例中的預設字串:「-- 請選擇」。

items 屬性通常會填入項目物件的集合或陣列。itemValueitemLabel 參考這些項目物件的 Bean 屬性(如果已指定)。否則,項目物件本身會轉換為字串。或者,您可以指定項目 Map,在這種情況下,Map 鍵會解譯為選項值,而 Map 值對應於選項標籤。如果也指定了 itemValueitemLabel(或兩者),則項目值屬性適用於 Map 鍵,而項目標籤屬性適用於 Map 值。

textarea 標籤

此標籤會呈現 HTML textarea 元素。以下 HTML 顯示其典型的輸出

<tr>
	<td>Notes:</td>
	<td><form:textarea path="notes" rows="3" cols="20"/></td>
	<td><form:errors path="notes"/></td>
</tr>

hidden 標籤

此標籤會呈現一個 HTML input 標籤,其 type 屬性設定為 hidden 並帶有綁定的值。若要提交未綁定的隱藏值,請使用 HTML input 標籤,並將 type 屬性設定為 hidden。以下 HTML 顯示其典型的輸出

<form:hidden path="house"/>

如果我們選擇將 house 值作為隱藏值提交,則 HTML 將如下所示

<input name="house" type="hidden" value="Gryffindor"/>

errors 標籤

此標籤會在 HTML span 元素中呈現欄位錯誤。它提供對控制器中建立的錯誤或與控制器相關聯的任何驗證器所建立的錯誤的存取權。

假設我們想要在提交表單後顯示 firstNamelastName 欄位的所有錯誤訊息。我們有一個名為 UserValidatorUser 類別實例的驗證器,如下例所示

  • Java

  • Kotlin

public class UserValidator implements Validator {

	public boolean supports(Class candidate) {
		return User.class.isAssignableFrom(candidate);
	}

	public void validate(Object obj, Errors errors) {
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
	}
}
class UserValidator : Validator {

	override fun supports(candidate: Class<*>): Boolean {
		return User::class.java.isAssignableFrom(candidate)
	}

	override fun validate(obj: Any, errors: Errors) {
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.")
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.")
	}
}

form.jsp 可能如下所示

<form:form>
	<table>
		<tr>
			<td>First Name:</td>
			<td><form:input path="firstName"/></td>
			<%-- Show errors for firstName field --%>
			<td><form:errors path="firstName"/></td>
		</tr>

		<tr>
			<td>Last Name:</td>
			<td><form:input path="lastName"/></td>
			<%-- Show errors for lastName field --%>
			<td><form:errors path="lastName"/></td>
		</tr>
		<tr>
			<td colspan="3">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form:form>

如果我們提交一個在 firstNamelastName 欄位中具有空值的表單,則 HTML 將如下所示

<form method="POST">
	<table>
		<tr>
			<td>First Name:</td>
			<td><input name="firstName" type="text" value=""/></td>
			<%-- Associated errors to firstName field displayed --%>
			<td><span name="firstName.errors">Field is required.</span></td>
		</tr>

		<tr>
			<td>Last Name:</td>
			<td><input name="lastName" type="text" value=""/></td>
			<%-- Associated errors to lastName field displayed --%>
			<td><span name="lastName.errors">Field is required.</span></td>
		</tr>
		<tr>
			<td colspan="3">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form>

如果我們想要顯示給定頁面的整個錯誤列表怎麼辦?下一個範例顯示 errors 標籤也支援一些基本萬用字元功能。

  • path="*":顯示所有錯誤。

  • path="lastName":顯示與 lastName 欄位相關聯的所有錯誤。

  • 如果省略 path,則僅顯示物件錯誤。

以下範例顯示頁面頂端的錯誤列表,然後是欄位特定錯誤在欄位旁邊

<form:form>
	<form:errors path="*" cssClass="errorBox"/>
	<table>
		<tr>
			<td>First Name:</td>
			<td><form:input path="firstName"/></td>
			<td><form:errors path="firstName"/></td>
		</tr>
		<tr>
			<td>Last Name:</td>
			<td><form:input path="lastName"/></td>
			<td><form:errors path="lastName"/></td>
		</tr>
		<tr>
			<td colspan="3">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form:form>

HTML 將如下所示

<form method="POST">
	<span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
	<table>
		<tr>
			<td>First Name:</td>
			<td><input name="firstName" type="text" value=""/></td>
			<td><span name="firstName.errors">Field is required.</span></td>
		</tr>

		<tr>
			<td>Last Name:</td>
			<td><input name="lastName" type="text" value=""/></td>
			<td><span name="lastName.errors">Field is required.</span></td>
		</tr>
		<tr>
			<td colspan="3">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form>

spring-form.tld 標籤庫描述符 (TLD) 包含在 spring-webmvc.jar 中。如需個別標籤的完整參考,請瀏覽 API 參考文件 或參閱標籤庫描述。

HTTP 方法轉換

REST 的一個關鍵原則是使用「統一介面」。這表示所有資源 (URL) 都可以使用相同的四種 HTTP 方法來操作:GET、PUT、POST 和 DELETE。對於每種方法,HTTP 規範都定義了確切的語意。例如,GET 應始終是安全的操作,表示它沒有副作用,而 PUT 或 DELETE 應是等冪的,表示您可以重複這些操作一遍又一遍,但最終結果應相同。雖然 HTTP 定義了這四種方法,但 HTML 僅支援兩種:GET 和 POST。幸運的是,有兩種可能的解決方法:您可以使用 JavaScript 來執行 PUT 或 DELETE,或者您可以執行 POST,並將「真實」方法作為附加參數(在 HTML 表單中建模為隱藏的輸入欄位)。Spring 的 HiddenHttpMethodFilter 使用了後一種技巧。此篩選器是一個純 Servlet 篩選器,因此,它可以與任何 Web 框架(不僅僅是 Spring MVC)結合使用。將此篩選器新增至您的 web.xml,並將帶有隱藏 method 參數的 POST 轉換為對應的 HTTP 方法請求。

為了支援 HTTP 方法轉換,Spring MVC 表單標籤已更新為支援設定 HTTP 方法。例如,以下程式碼片段來自 Pet Clinic 範例

<form:form method="delete">
	<p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>

前面的範例執行 HTTP POST,其中「真實」的 DELETE 方法隱藏在請求參數後面。它由 web.xml 中定義的 HiddenHttpMethodFilter 拾取,如下例所示

<filter>
	<filter-name>httpMethodFilter</filter-name>
	<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
	<filter-name>httpMethodFilter</filter-name>
	<servlet-name>petclinic</servlet-name>
</filter-mapping>

以下範例顯示對應的 @Controller 方法

  • Java

  • Kotlin

@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
	this.clinic.deletePet(petId);
	return "redirect:/owners/" + ownerId;
}
@RequestMapping(method = [RequestMethod.DELETE])
fun deletePet(@PathVariable ownerId: Int, @PathVariable petId: Int): String {
	clinic.deletePet(petId)
	return "redirect:/owners/$ownerId"
}

HTML5 標籤

Spring 表單標籤庫允許輸入動態屬性,這表示您可以輸入任何 HTML5 特定屬性。

表單 input 標籤支援輸入 text 以外的 type 屬性。這旨在允許呈現新的 HTML5 特定輸入類型,例如 emaildaterange 等。請注意,不需要輸入 type='text',因為 text 是預設類型。