19.5 JSP & JSTL

Spring为JSP和JSTL视图提供了几个开箱即用的解决方案。 使用JSP或JSTL是使用在WebApplicationContext中定义的常规视图解析器完成的。 此外,当然,您需要编写一些实际渲染视图的JSP。

设置应用程序使用JSTL是一个常见的错误源,主要是由于混淆了不同的servlet规范JSP和JSTL版本号,它们的含义和如何正确声明taglibs引起的。 文章

How to Reference and Use JSTL in your Web Application

提供了常见的陷阱和如何避免它们的有用指南。 请注意,从Spring 3.0开始,最小支持的servlet版本为2.4(JSP 2.0和JSTL 1.1),这有助于减少混淆的范围。

19.5.1 视图解析

就像您与Spring集成的任何其他视图技术一样,对于JSP,您将需要一个视图解析器来解析你的视图。 使用JSP进行开发时最常用的视图解析器是InternalResourceViewResolver和ResourceBundleViewResolver。 两者都在WebApplicationContext中声明:

  1. <!-- the ResourceBundleViewResolver -->
  2. <bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
  3. <property name="basename" value="views"/>
  4. </bean>
  5. # And a sample properties file is uses (views.properties in WEB-INF/classes):
  6. welcome.(class)=org.springframework.web.servlet.view.JstlView
  7. welcome.url=/WEB-INF/jsp/welcome.jsp
  8. productList.(class)=org.springframework.web.servlet.view.JstlView
  9. productList.url=/WEB-INF/jsp/productlist.jsp

如您所见,ResourceBundleViewResolver需要一个属性文件来定义映射到1)一个类和2个URL的视图名称。 使用ResourceBundleViewResolver,您只能使用一个解析器来混合不同类型的视图。

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

InternalResourceBundleViewResolver可以配置为如上所述使用JSP。 作为最佳做法,我们强烈建议您将JSP文件放在“WEB-INF”目录下的目录中,以免客户端直接访问。

19.5.2 ‘Plain-old’ JSPs 与JSTL

当使用Java标准标签库时,必须使用特殊的视图类JstlView,因为JSTL需要一些准备工作,例如I18N功能才能正常工作。

19.5.3 Additional tags facilitating development

Spring提供了请求参数到命令对象的数据绑定,如前几章所述。 为了方便JSP页面的开发与这些数据绑定功能的结合,Spring提供了一些使事情更容易的标签。 所有Spring标签都具有HTML转义功能,以启用或禁用字符转义。

标签库描述符(TLD)包含在spring-webmvc.jar中。 有关各个标签的更多信息,请参见附录“???”。

19.5.4 使用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页面的顶部:

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

其中form是要用于此库的标签的标签名称前缀。

表单标签

此标记呈现HTML“form”标签,并将绑定路径公开到内部标签以进行绑定。 它将命令对象放在PageContext中,以便可以通过内部标记访问命令对象。 该库中的所有其他标签都是表单标签的嵌套标签。

假设我们有一个名为User的域对象。 它是一个具有诸如firstName和lastName等属性的JavaBean。 我们将使用它作为返回form.jsp的表单控制器的表单支持对象。 下面是一个form.jsp的例子:

  1. <form:form>
  2. <table>
  3. <tr>
  4. <td>First Name:</td>
  5. <td><form:input path="firstName"/></td>
  6. </tr>
  7. <tr>
  8. <td>Last Name:</td>
  9. <td><form:input path="lastName"/></td>
  10. </tr>
  11. <tr>
  12. <td colspan="2">
  13. <input type="submit" value="Save Changes"/>
  14. </td>
  15. </tr>
  16. </table>
  17. </form:form>

firstName和lastName值由页面控制器从PageContext中放置的命令对象中检索。 继续阅读以查看更多关于内部标签如何与表单标签一起使用的复杂示例。

生成的HTML看起来像一个标准格式:

  1. <form method="POST">
  2. <table>
  3. <tr>
  4. <td>First Name:</td>
  5. <td><input name="firstName" type="text" value="Harry"/></td>
  6. </tr>
  7. <tr>
  8. <td>Last Name:</td>
  9. <td><input name="lastName" type="text" value="Potter"/></td>
  10. </tr>
  11. <tr>
  12. <td colspan="2">
  13. <input type="submit" value="Save Changes"/>
  14. </td>
  15. </tr>
  16. </table>
  17. </form>

前面的JSP假定表单后备对象的变量名是’command’。 如果您将表单支持对象放在另一个名称下的模型中(绝对是最佳实践),那么可以将表单绑定到命名变量,如下所示:

  1. <form:form modelAttribute="user">
  2. <table>
  3. <tr>
  4. <td>First Name:</td>
  5. <td><form:input path="firstName"/></td>
  6. </tr>
  7. <tr>
  8. <td>Last Name:</td>
  9. <td><form:input path="lastName"/></td>
  10. </tr>
  11. <tr>
  12. <td colspan="2">
  13. <input type="submit" value="Save Changes"/>
  14. </td>
  15. </tr>
  16. </table>
  17. </form:form>

输入标签

此标记默认使用绑定值和type =’text’呈现HTML“输入”标签。 有关此标记的示例,请参阅“The form tag”一节。 从Spring 3.1开始,您可以使用其他类型的HTML5特定类型,如“email”,“tel”,“date”等。

勾选标签

此标记会显示一个类型为“checkbox”的HTML“input”标签。让我们假设我们的用户有喜好,如通讯订阅和爱好列表。 以下是Preferences类的示例:

  1. public class Preferences {
  2. private boolean receiveNewsletter;
  3. private String[] interests;
  4. private String favouriteWord;
  5. public boolean isReceiveNewsletter() {
  6. return receiveNewsletter;
  7. }
  8. public void setReceiveNewsletter(boolean receiveNewsletter) {
  9. this.receiveNewsletter = receiveNewsletter;
  10. }
  11. public String[] getInterests() {
  12. return interests;
  13. }
  14. public void setInterests(String[] interests) {
  15. this.interests = interests;
  16. }
  17. public String getFavouriteWord() {
  18. return favouriteWord;
  19. }
  20. public void setFavouriteWord(String favouriteWord) {
  21. this.favouriteWord = favouriteWord;
  22. }
  23. }

The form.jsp would look like:

  1. <form:form>
  2. <table>
  3. <tr>
  4. <td>Subscribe to newsletter?:</td>
  5. <%-- Approach 1: Property is of type java.lang.Boolean --%>
  6. <td><form:checkbox path="preferences.receiveNewsletter"/></td>
  7. </tr>
  8. <tr>
  9. <td>Interests:</td>
  10. <%-- Approach 2: Property is of an array or of type java.util.Collection --%>
  11. <td>
  12. Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
  13. Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
  14. Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
  15. </td>
  16. </tr>
  17. <tr>
  18. <td>Favourite Word:</td>
  19. <%-- Approach 3: Property is of type java.lang.Object --%>
  20. <td>
  21. Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>
  22. </td>
  23. </tr>
  24. </table>
  25. </form:form>

复选框标签有3种方法可以满足您所有的复选框需求。

  • 方法一 – 当绑定值的类型为java.lang.Boolean时,如果绑定值为true,则输入(复选框)将被标记为“已检查”。 valueattribute对应于setValue(Object)value属性的已解析值。
  • 方法二 – 当绑定值为array或java.util.Collection类型时,如果配置的setValue(Object)值存在于绑定集合中,则输入(复选框)将被标记为“checked”.
  • 方法三 – 对于任何其他绑定值类型,如果配置的setValue(Object)等于绑定值,则输入(复选框)将被标记为“已检查”。

请注意,无论采用何种方法,都会生成相同的HTML结构。 以下是某些复选框的HTML片段:

  1. <tr>
  2. <td>Interests:</td>
  3. <td>
  4. Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
  5. <input type="hidden" value="1" name="_preferences.interests"/>
  6. Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
  7. <input type="hidden" value="1" name="_preferences.interests"/>
  8. Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
  9. <input type="hidden" value="1" name="_preferences.interests"/>
  10. </td>
  11. </tr>

您可能不会看到的是每个复选框后面的额外的隐藏字段。 当HTML页面中的复选框未被选中时,一旦表单被提交,它的值就不会作为HTTP请求参数的一部分发送到服务器,所以我们需要一个解决方法来为HTML解决这个问题,以便Spring表单数据绑定 工作。 复选框标记遵循现有的Spring约定,为每个复选框添加一个前缀为下划线(“_”)的隐藏参数。 通过这样做,您正在有效地告诉Spring,“复选框在表单中可见,并且我希望我的对象将窗体数据绑定到其中,以反映该复选框的状态,无论什么”。

复选框标记

此标记会使用“checkbox”类型呈现多个HTML“input”标签。

基于上一个复选框标记部分的示例。 有时您不希望在JSP页面中列出所有可能的选项。 您希望在运行时提供可用选项的列表,并将其传递给标签。 这就是复选框标签的目的。 您传递一个数组,列表或地图,其中包含“items”属性中的可用选项。 通常,bound属性是一个集合,因此它可以保存用户选择的多个值。 以下是使用此标记的JSP的示例:

  1. <form:form>
  2. <table>
  3. <tr>
  4. <td>Interests:</td>
  5. <td>
  6. <%-- Property is of an array or of type java.util.Collection --%>
  7. <form:checkboxes path="preferences.interests" items="${interestList}"/>
  8. </td>
  9. </tr>
  10. </table>
  11. </form:form>

此示例假定“兴趣列表”是可用作包含要从中选择的值的字符串的模型属性的列表。 在使用地图的情况下,将使用地图条目键作为值,并将地图条目的值用作要显示的标签。 您还可以使用自定义对象,您可以在其中使用“itemValue”提供值的属性名称,并使用“itemLabel”提供标签。

单选标记

此标记呈现类型为“radio”的HTML“input”标签。典型的使用模式将涉及绑定到相同属性但具有不同值的多个标签实例。

  1. <tr>
  2. <td>Sex:</td>
  3. <td>
  4. Male: <form:radiobutton path="sex" value="M"/> <br/>
  5. Female: <form:radiobutton path="sex" value="F"/>
  6. </td>
  7. </tr>

多个单选框标签

此标签使用“radio”类型呈现多个HTML“input”标签。

就像上面的复选框标签一样,您可能希望将可用选项作为运行时变量传递。 对于这种用法,您将使用radiobuttons标签。 您传递一个数组,列表或地图,其中包含“items”属性中的可用选项。 在使用地图的情况下,将使用地图条目键作为值,并将地图条目的值用作要显示的标签。 您还可以使用自定义对象,您可以在其中使用“itemValue”提供值的属性名称,并使用“itemLabel”提供标签。

  1. <tr>
  2. <td>Sex:</td>
  3. <td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
  4. </tr>

密码标签

此标记使用绑定值呈现类型为“password”的HTML“input”标签。

  1. <tr>
  2. <td>Password:</td>
  3. <td>
  4. <form:password path="password"/>
  5. </td>
  6. </tr>

请注意,默认情况下,密码值未显示。 如果您想要显示密码值,则将“showPassword”属性的值设置为true,如此。

  1. <tr>
  2. <td>Password:</td>
  3. <td>
  4. <form:password path="password" value="^76525bvHGq" showPassword="true"/>
  5. </td>
  6. </tr>

选择标签

此标记呈现HTML“选择”元素。 它支持与选定选项的数据绑定以及嵌套选项和选项标签的使用。

假设用户有一个技能列表。

  1. <tr>
  2. <td>Skills:</td>
  3. <td><form:select path="skills" items="${skills}"/></td>
  4. </tr>

如果用户的技能在Herbology,“技能”行的HTML源代码将如下所示:

  1. <tr>
  2. <td>Skills:</td>
  3. <td>
  4. <select name="skills" multiple="true">
  5. <option value="Potions">Potions</option>
  6. <option value="Herbology" selected="selected">Herbology</option>
  7. <option value="Quidditch">Quidditch</option>
  8. </select>
  9. </td>
  10. </tr>

可选 标签

此标记呈现HTML“选项”。 它根据绑定值适当地设置“选择”。

  1. <tr>
  2. <td>House:</td>
  3. <td>
  4. <form:select path="house">
  5. <form:option value="Gryffindor"/>
  6. <form:option value="Hufflepuff"/>
  7. <form:option value="Ravenclaw"/>
  8. <form:option value="Slytherin"/>
  9. </form:select>
  10. </td>
  11. </tr>

如果用户的房子在Gryffindor,“房子”行的HTML源代码将如下所示:

  1. <tr>
  2. <td>House:</td>
  3. <td>
  4. <select name="house">
  5. <option value="Gryffindor" selected="selected">Gryffindor</option>
  6. <option value="Hufflepuff">Hufflepuff</option>
  7. <option value="Ravenclaw">Ravenclaw</option>
  8. <option value="Slytherin">Slytherin</option>
  9. </select>
  10. </td>
  11. </tr>

多个可选标签

此标记呈现HTML“option”标签的列表。 它根据绑定值适当地设置’selected’属性。

  1. <tr>
  2. <td>Country:</td>
  3. <td>
  4. <form:select path="country">
  5. <form:option value="-" label="--Please Select"/>
  6. <form:options items="${countryList}" itemValue="code" itemLabel="name"/>
  7. </form:select>
  8. </td>
  9. </tr>

如果用户居住在英国,“Country”行的HTML源代码将如下所示:

  1. <tr>
  2. <td>Country:</td>
  3. <td>
  4. <select name="country">
  5. <option value="-">--Please Select</option>
  6. <option value="AT">Austria</option>
  7. <option value="UK" selected="selected">United Kingdom</option>
  8. <option value="US">United States</option>
  9. </select>
  10. </td>
  11. </tr>

如示例所示,选项标签与选项标签的组合使用将生成相同的标准HTML,但允许您显式指定JSP中仅用于显示(在其所在的位置)的值,例如 示例:“ - 请选择”。

items属性通常用一组或多个项目对象填充。 itemValue和itemLabel只是指定这些项目对象的bean属性; 否则,项目对象本身将被字符串化。 或者,您可以指定项目地图,在这种情况下,地图键将被解释为选项值,地图值对应于选项标签。 如果同时指定了itemValue和/或itemLabel,则item value属性将应用于map键,item label属性将应用于map值。

textarea标签

此标记呈现HTML“textarea”。

  1. <tr>
  2. <td>Notes:</td>
  3. <td><form:textarea path="notes" rows="3" cols="20"/></td>
  4. <td><form:errors path="notes"/></td>
  5. </tr>

hidden 标签

此标记使用绑定值呈现类型为“hidden”的HTML“input”标签。 要提交未绑定的隐藏值,请使用类型为“hidden”的HTML输入标签。

<form:hidden path =“house”/>
如果我们选择将“房屋”值提交为隐藏值,则HTML将如下所示:

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

errors 标签

此标记会在HTML’span’标签中呈现字段错误。 它提供对控制器中创建的错误的访问或由与控制器相关联的任何验证器创建的错误。

假设我们要在提交表单时显示firstName和lastName字段的所有错误消息。 我们有一个名为UserValidator的User类的实例的验证器。

  1. public class UserValidator implements Validator {
  2. public boolean supports(Class candidate) {
  3. return User.class.isAssignableFrom(candidate);
  4. }
  5. public void validate(Object obj, Errors errors) {
  6. ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
  7. ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
  8. }
  9. }

form.jsp如下所示:

  1. <form:form>
  2. <table>
  3. <tr>
  4. <td>First Name:</td>
  5. <td><form:input path="firstName"/></td>
  6. <%-- Show errors for firstName field --%>
  7. <td><form:errors path="firstName"/></td>
  8. </tr>
  9. <tr>
  10. <td>Last Name:</td>
  11. <td><form:input path="lastName"/></td>
  12. <%-- Show errors for lastName field --%>
  13. <td><form:errors path="lastName"/></td>
  14. </tr>
  15. <tr>
  16. <td colspan="3">
  17. <input type="submit" value="Save Changes"/>
  18. </td>
  19. </tr>
  20. </table>
  21. </form:form>

如果我们在firstName和lastName字段中提交一个带有空值的表单,这就是HTML的样子:

  1. <form method="POST">
  2. <table>
  3. <tr>
  4. <td>First Name:</td>
  5. <td><input name="firstName" type="text" value=""/></td>
  6. <%-- Associated errors to firstName field displayed --%>
  7. <td><span name="firstName.errors">Field is required.</span></td>
  8. </tr>
  9. <tr>
  10. <td>Last Name:</td>
  11. <td><input name="lastName" type="text" value=""/></td>
  12. <%-- Associated errors to lastName field displayed --%>
  13. <td><span name="lastName.errors">Field is required.</span></td>
  14. </tr>
  15. <tr>
  16. <td colspan="3">
  17. <input type="submit" value="Save Changes"/>
  18. </td>
  19. </tr>
  20. </table>
  21. </form>

如果我们要显示给定页面的整个错误列表怎么办? 下面的示例显示了errors标签还支持一些基本的通配符功能。

  • path="*"– 显示所有的错误
  • path="lastName"– 显示与lastName 相关的错误
  • 假如path省略- 仅显示对象错误

下面的示例将显示页面顶部的错误列表,后跟字段旁边的字段特定错误:

  1. <form:form>
  2. <form:errors path="*" cssClass="errorBox"/>
  3. <table>
  4. <tr>
  5. <td>First Name:</td>
  6. <td><form:input path="firstName"/></td>
  7. <td><form:errors path="firstName"/></td>
  8. </tr>
  9. <tr>
  10. <td>Last Name:</td>
  11. <td><form:input path="lastName"/></td>
  12. <td><form:errors path="lastName"/></td>
  13. </tr>
  14. <tr>
  15. <td colspan="3">
  16. <input type="submit" value="Save Changes"/>
  17. </td>
  18. </tr>
  19. </table>
  20. </form:form>

HTML是这样的:

  1. <form method="POST">
  2. <span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
  3. <table>
  4. <tr>
  5. <td>First Name:</td>
  6. <td><input name="firstName" type="text" value=""/></td>
  7. <td><span name="firstName.errors">Field is required.</span></td>
  8. </tr>
  9. <tr>
  10. <td>Last Name:</td>
  11. <td><input name="lastName" type="text" value=""/></td>
  12. <td><span name="lastName.errors">Field is required.</span></td>
  13. </tr>
  14. <tr>
  15. <td colspan="3">
  16. <input type="submit" value="Save Changes"/>
  17. </td>
  18. </tr>
  19. </table>
  20. </form>

HTTP 方法转变

REST的一个关键原则是使用统一接口。这意味着可以使用相同的四种HTTP方法来操作所有资源(URL):GET,PUT,POST和DELETE。对于每个方法,HTTP规范定义了准确的语义。例如,GET应该永远是一个安全的操作,这意味着没有副作用,PUT或DELETE应该是幂等的,这意味着你可以一遍又一遍地重复这些操作,但最终结果应该是一样的。虽然HTTP定义了这四种方法,但HTML只支持两种方法:GET和POST。幸运的是,有两种可能的解决方法:您可以使用JavaScript来执行您的PUT或DELETE,或者使用“real”方法作为附加参数(在HTML表单中建模为隐藏输入字段)进行POST。后一个技巧是Spring的HiddenHttpMethodFilter所做的。此过滤器是一个简单的Servlet过滤器,因此可以与任何Web框架(不仅仅是Spring MVC)结合使用。只需将此过滤器添加到您的web.xml中,并将带有隐藏_method参数的POST转换为相应的HTTP方法请求。

为了支持HTTP方法转换,Spring MVC表单标记已更新,以支持设置HTTP方法。例如,从更新的Petclinic示例中获取以下代码段

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

这将实际执行HTTP POST,隐藏在请求参数之后的“real”DELETE方法,由HiddenHttpMethodFilter拾取,如web.xml中所定义:

  1. <filter>
  2. <filter-name>httpMethodFilter</filter-name>
  3. <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>httpMethodFilter</filter-name>
  7. <servlet-name>petclinic</servlet-name>
  8. </filter-mapping>

相应的@Controller方法如下所示:

  1. @RequestMapping(method = RequestMethod.DELETE)
  2. public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
  3. this.clinic.deletePet(petId);
  4. return "redirect:/owners/" + ownerId;
  5. }

HTML5 标签

从Spring 3开始,Spring表单标签库允许输入动态属性,这意味着您可以输入任何特定于HTML5的属性。

在Spring 3.1中,表单输入标签支持输入“text”以外的类型属性。 这旨在允许渲染新的HTML5特定输入类型,如 ’email’, ‘date’, ‘range’等。 请注意,输入type =’text’不是必需的,因为’text’是默认类型。