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
是您希望用于此库中标签的标签名称前缀。
表单标签
此标签渲染一个 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
标签,其类型设置为 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> [id="CO1-1"]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> [id="CO2-1"]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
字段的所有错误消息。
我们有一个名为 UserValidator
的 User
类实例验证器,如以下示例所示:
-
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"
}