JSP 和 JSTL
Spring Framework 內建了將 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>
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
是你希望為此標籤庫中的標籤使用的標籤名字首。
form 標籤
此標籤渲染一個 HTML 'form' 元素,並向內部標籤暴露繫結路徑以進行繫結。它將命令物件放入 PageContext
中,以便內部標籤可以訪問該命令物件。此庫中的所有其他標籤都是 form
標籤的巢狀標籤。
假設我們有一個名為 User
的域物件。它是一個包含 firstName
和 lastName
等屬性的 JavaBean。我們可以將其用作表單控制器的表單支援物件,該控制器返回 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>
firstName
和 lastName
的值是從頁面控制器放置在 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
標籤
此標籤預設渲染一個 HTML input
元素,帶有繫結值和 type='text'
。有關此標籤的示例,請參見form標籤。你還可以使用 HTML5 特定的型別,例如 email
、tel
、date
等。
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)
會被標記為checked
。value
屬性對應於setValue(Object)
值屬性的解析值。 -
第二種方法:當繫結值的型別是
array
或java.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
標籤的作用。你可以傳遞一個 Array
、List
或 Map
,其中包含 items
屬性中可用的選項。通常,繫結屬性是一個集合,以便它可以儲存使用者選擇的多個值。以下示例展示了一個使用此標籤的 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
標籤。你可以傳遞一個 Array
、List
或 Map
,其中包含 items
屬性中可用的選項。如果你使用 Map
,則 Map 條目的鍵用作值,而 Map 條目的值用作要顯示的標籤。你還可以使用自定義物件,透過使用 itemValue
提供值的屬性名,並透過使用 itemLabel
提供標籤的屬性名,如下例所示:
<tr>
<td>Sex:</td>
<td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>
password
標籤
此標籤渲染一個 HTML input
標籤,其型別設定為 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' 元素。它支援對選定選項進行資料繫結,也支援使用巢狀的 option
和 options
標籤。
假設一個 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
屬性通常填充一個包含專案物件的集合或陣列。如果指定了 itemValue
和 itemLabel
,它們引用這些專案物件的 bean 屬性。否則,專案物件本身將轉換為字串。或者,你可以指定一個專案的 Map
,在這種情況下,Map 鍵被解釋為選項值,而 Map 值對應於選項標籤。如果同時也指定了 itemValue
或 itemLabel
(或兩者),則專案值屬性適用於 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
,帶有繫結值。要提交未繫結(非資料繫結)的隱藏值,請使用 type
設定為 hidden
的 HTML input
標籤。以下 HTML 顯示了典型的輸出:
<form:hidden path="house"/>
如果我們選擇將 house
值作為隱藏值提交,HTML 將如下所示:
<input name="house" type="hidden" value="Gryffindor"/>
errors
標籤
此標籤在 HTML span
元素中渲染欄位錯誤。它提供了訪問你的控制器中建立的錯誤或與你的控制器關聯的任何校驗器建立的錯誤的功能。
假設我們希望在提交表單後顯示 firstName
和 lastName
欄位的所有錯誤訊息。我們有一個用於 User
類例項的校驗器,名為 UserValidator
,如下例所示:
-
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>
如果我們在 firstName
和 lastName
欄位中提交空值,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"
}