JSP和JSTL
Spring Framework内置了与JSP和JSTL一起使用Spring MVC的集成。
视图解析器
在使用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
的领域对象。它是一个具有诸如firstName
和lastName
等属性的JavaBean。我们可以将其用作我们的表单控制器的表单后备对象,该控制器返回form.jsp
。以下示例展示了form.jsp
可能是什么样子:
<form:form>
<table>
<tr>
<td>名字:</td>
<td><form:input path="firstName"/></td>
</tr>
<tr>
<td>姓氏:</td>
<td><form:input path="lastName"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="保存更改"/>
</td>
</tr>
</table>
</form:form>
firstName
和lastName
的值是从页面控制器放置在PageContext
中的命令对象中检索的。继续阅读以查看如何在form
标签中使用内部标签的更复杂示例。
以下清单显示了生成的HTML,看起来像一个标准表单:
<form method="POST">
<table>
<tr>
<td>名字:</td>
<td><input name="firstName" type="text" value="Harry"/></td>
</tr>
<tr>
<td>姓氏:</td>
<td><input name="lastName" type="text" value="Potter"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="保存更改"/>
</td>
</tr>
</table>
</form>
上述JSP假设表单后备对象的变量名为command
。如果您将表单后备对象放入模型中的另一个名称下(绝对是最佳实践),您可以将表单绑定到命名变量,如下例所示:
<form:form modelAttribute="user">
<table>
<tr>
<td>名字:</td>
<td><form:input path="firstName"/></td>
</tr>
<tr>
<td>姓氏:</td>
<td><form:input path="lastName"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="保存更改"/>
</td>
</tr>
</table>
</form:form>
input
标签
此标签使用绑定值默认呈现带有type='text'
的HTML input
元素。有关此标签的示例,请参见表单标签。您还可以使用HTML5特定类型,如email
、tel
、date
等。
checkbox
标签
此标签使用type
设置为checkbox
的HTML input
标签。
假设我们的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>订阅新闻?:</td>
<%-- 方法1:属性为java.lang.Boolean类型 --%>
<td><form:checkbox path="preferences.receiveNewsletter"/></td>
</tr>
<tr>
<td>兴趣爱好:</td>
<%-- 方法2:属性为数组或java.util.Collection类型 --%>
<td>
魁地奇:<form:checkbox path="preferences.interests" value="Quidditch"/>
草药学:<form:checkbox path="preferences.interests" value="Herbology"/>
黑魔法防御术:<form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
</td>
</tr>
<tr>
<td>最喜欢的词汇:</td>
<%-- 方法3:属性为java.lang.Object类型 --%>
<td>
魔法:<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>兴趣爱好:</td>
<td>
魁地奇:<input name="preferences.interests" type="checkbox" value="Quidditch"/>
<input type="hidden" value="1" name="_preferences.interests"/>
草药学:<input name="preferences.interests" type="checkbox" value="Herbology"/>
<input type="hidden" value="1" name="_preferences.interests"/>
黑魔法防御术:<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
标签呈现为checkbox
类型。
本节基于前一个checkbox
标签部分的示例构建。有时,您可能不希望在JSP页面中列出所有可能的爱好。您更愿意在运行时提供一个可用选项列表,并将其传递给标签。这就是checkboxes
标签的目的。您可以在items
属性中传递包含可用选项的Array
、List
或Map
。通常,绑定属性是一个集合,以便它可以保存用户选择的多个值。以下示例显示了使用此标签的JSP:
<form:form>
<table>
<tr>
<td>兴趣爱好:</td>
<td>
<%-- 属性是数组或java.util.Collection类型 --%>
<form:checkboxes path="preferences.interests" items="${interestList}"/>
</td>
</tr>
</table>
</form:form>
此示例假定interestList
是作为模型属性可用的List
,其中包含要从中选择的值的字符串。如果使用Map
,则将使用映射条目键作为值,映射条目的值用作要显示的标签。您还可以使用自定义对象,在其中可以通过使用itemValue
提供值的属性名称,并通过使用itemLabel
提供标签,如下例所示:
radiobutton
标签
此标签将HTML input
元素呈现为radio
类型。
典型的使用模式涉及绑定到相同属性但具有不同值的多个标签实例,如下例所示:
<tr>
<td>性别:</td>
<td>
男性:<form:radiobutton path="sex" value="M"/> <br/>
女性:<form:radiobutton path="sex" value="F"/>
</td>
</tr>
radiobuttons
标签
此标签将多个HTML input
元素呈现为radio
类型。
与checkboxes
标签一样,您可能希望将可用选项作为运行时变量传递。对于此用法,您可以使用radiobuttons
标签。您可以在items
属性中传递包含可用选项的Array
、List
或Map
。如果使用Map
,则将使用映射条目键作为值,映射条目的值用作要显示的标签。您还可以使用自定义对象,在其中可以通过使用itemValue
提供值的属性名称,并通过使用itemLabel
提供标签,如下例所示:
password
标签
此标签将HTML input
标签呈现为类型设置为password
的标签,并绑定值。
<tr>
<td>密码:</td>
<td>
<form:password path="password"/>
</td>
</tr>
请注意,默认情况下,密码值不会显示。如果确实希望显示密码值,可以将showPassword
属性的值设置为true
,如下例所示:
option
标签
此标签呈现HTML option
元素。它根据绑定值设置selected
。以下HTML显示了其典型输出:
textarea
标签
此标签呈现一个HTML textarea
元素。以下HTML显示了它的典型输出:
<tr>
<td>注释:</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>名字:</td>
<td><form:input path="firstName"/></td>
<%-- 显示名字字段的错误 --%>
<td><form:errors path="firstName"/></td>
</tr>
<tr>
<td>姓氏:</td>
<td><form:input path="lastName"/></td>
<%-- 显示姓氏字段的错误 --%>
<td><form:errors path="lastName"/></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="保存更改"/>
</td>
</tr>
</table>
</form:form>
如果我们提交一个带有firstName
和lastName
字段中空值的表单,HTML将如下所示:
<form method="POST">
<table>
<tr>
<td>名字:</td>
<td><input name="firstName" type="text" value=""/></td>
<%-- 将错误关联到名字字段并显示 --%>
<td><span name="firstName.errors">字段是必需的。</span></td>
</tr>
<tr>
<td>姓氏:</td>
<td><input name="lastName" type="text" value=""/></td>
<%-- 将错误关联到姓氏字段并显示 --%>
<td><span name="lastName.errors">字段是必需的。</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="保存更改"/>
</td>
</tr>
</table>
</form>
如果我们想要显示给定页面的所有错误列表,下一个示例显示errors
标签还支持一些基本的通配符功能。
-
path="*"
:显示所有错误。 -
path="lastName"
:显示与lastName
字段相关的所有错误。 -
如果省略
path
,则仅显示对象错误。
以下示例在页面顶部显示错误列表,然后在字段旁边显示特定于字段的错误:
<form:form>
<form:errors path="*" cssClass="errorBox"/>
<table>
<tr>
<td>名字:</td>
<td><form:input path="firstName"/></td>
<td><form:errors path="firstName"/></td>
</tr>
<tr>
<td>姓氏:</td>
<td><form:input path="lastName"/></td>
<td><form:errors path="lastName"/></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="保存更改"/>
</td>
</tr>
</table>
</form:form>
HTML将如下所示:
<form method="POST">
<span name="*.errors" class="errorBox">字段是必需的。<br/>字段是必需的。</span>
<table>
<tr>
<td>名字:</td>
<td><input name="firstName" type="text" value=""/></td>
<td><span name="firstName.errors">字段是必需的。</span></td>
</tr>
<tr>
<td>姓氏:</td>
<td><input name="lastName" type="text" value=""/></td>
<td><span name="lastName.errors">字段是必需的。</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="保存更改"/>
</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方法隐藏在一个请求参数后面。它被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"
}