JSP 和 JSTL
Spring 框架內建了 Spring MVC 與 JSP 和 JSTL 的整合。
檢視解析器
在使用 JSP 進行開發時,你通常會宣告一個 InternalResourceViewResolver bean。
InternalResourceViewResolver 可用於分派到任何 Servlet 資源,特別是 JSP。作為最佳實踐,我們強烈建議將你的 JSP 檔案放在 WEB-INF 目錄下的子目錄中,這樣客戶端就無法直接訪問。
下面的配置正是這樣做的,它註冊了一個 JSP 檢視解析器,使用預設的檢視名稱字首 "/WEB-INF/" 和預設的字尾 ".jsp"。
-
Java
-
XML
@EnableWebMvc
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp();
}
}
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:jsp/>
</mvc:view-resolvers>
| 你可以指定自定義的字首和字尾。 |
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,具有 firstName 和 lastName 等屬性。我們可以將其用作表單控制器的表單支援物件,該控制器返回 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'。有關此標籤的示例,請參見 表單標籤。你還可以使用 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 標籤的目的。你可以在 items 屬性中傳入一個包含可用選項的 Array、List 或 Map。通常,繫結的屬性是一個集合,以便它可以儲存使用者選擇的多個值。以下示例顯示了一個使用此標籤的 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,則對映條目鍵用作值,對映條目的值用作要顯示的標籤。你還可以使用自定義物件,其中可以使用 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 屬性中傳入一個包含可用選項的 Array、List 或 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' 元素。它支援資料繫結到選定的選項以及巢狀的 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,在這種情況下,對映鍵被解釋為選項值,對映值對應於選項標籤。如果 itemValue 或 itemLabel(或兩者)也被指定,則專案值屬性適用於對映鍵,專案標籤屬性適用於對映值。
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 元素中渲染欄位錯誤。它提供了訪問控制器中建立的錯誤或與控制器關聯的任何驗證器建立的錯誤。
假設我們希望在提交表單後顯示 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,或者你可以透過附加引數(在 HTML 表單中模擬為隱藏輸入欄位)進行 POST,其中包含“真實”方法。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 方法隱藏在請求引數後面。它由 HiddenHttpMethodFilter 捕獲,該過濾器在 web.xml 中定義,如以下示例所示
<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"
}