返回介绍

6.2.2 使用 Spring 的 JSP 库

发布于 2024-08-17 00:45:50 字数 17935 浏览 0 评论 0 收藏 0

当为JSP添加功能时,标签库是一种很强大的方式,能够避免在脚本块中直接编写Java代码。Spring提供了两个JSP标签库,用来帮助定义Spring MVC Web的视图。其中一个标签库会用来渲染HTML表单标签,这些标签可以绑定model中的某个属性。另外一个标签库包含了一些工具类标签,我们随时都可以非常便利地使用它们。

在这两个标签库中,你可能会发现表单绑定的标签库更加有用。所以,我们就从这个标签库开始学习Spring的JSP标签。我们将会看到如何将Spittr应用的注册表单绑定到模型上,这样表单就可以预先填充值,并且在表单提交失败后,能够展现校验错误。

将表单绑定到模型上

Spring的表单绑定JSP标签库包含了14个标签,它们中的大多数都用来渲染HTML中的表单标签。但是,它们与原生HTML标签的区别在于它们会绑定模型中的一个对象,能够根据模型中对象的属性填充值。标签库中还包含了一个为用户展现错误的标签,它会将错误信息渲染到最终的HTML之中。

为了使用表单绑定库,需要在JSP页面中对其进行声明:

需要注意,我将前缀指定为“sf”,但通常也可能使用“form”前缀。你可以选择任意喜欢的前缀,我之所以选择“sf”是因为它很简洁、易于输入,并且还是Spring form的简写形式。在本书中,当使用表单绑定库的时候,我会一直使用“sf”前缀。

在声明完表单绑定标签库之后,你就可以使用14个相关的标签了。如表6.2所示。

表6.2 借助Spring表单绑定标签库中所包含的标签,我们能够将模型对象绑定到渲染后的HTML表单中

JSP标签

描  述

<sf:checkbox>

渲染成一个HTML <input>标签,其中type属性设置为checkbox

<sf:checkboxes>

渲染成多个HTML <input>标签,其中type属性设置为checkbox

<sf:errors>

在一个HTML <span>中渲染输入域的错误

<sf:form>

渲染成一个HTML <form>标签,并为其内部标签暴露绑定路径,用于数据绑定

<sf:hidden>

渲染成一个HTML <input>标签,其中type属性设置为hidden

<sf:input>

渲染成一个HTML <input>标签,其中type属性设置为text

<sf:label>

渲染成一个HTML <label>标签

<sf:option>

渲染成一个HTML <option>标签,其selected属性根据所绑定的值进行设置

<sf:options>

按照绑定的集合、数组或Map,渲染成一个HTML <option>标签的列表

<sf:password>

渲染成一个HTML <input>标签,其中type属性设置为password

<sf:radiobutton>

渲染成一个HTML <input>标签,其中type属性设置为radio

<sf:radiobuttons>

渲染成多个HTML <input>标签,其中type属性设置为radio

<sf:select>

渲染为一个HTML <select>标签

<sf:textarea>

渲染为一个HTML <textarea>标签

要在一个样例中介绍所有的这些标签是很困难的,如果一定要这样做的话,肯定也会非常牵强。就Spittr样例来说,我们只会用到适合于Spittr应用中注册表单的标签。具体来讲,也就是<sf:form>、<sf:input>和<sf:password>。在注册JSP中使用这些标签后,所得到的程序如下所示:

<sf:form>会渲染会一个HTML <form>标签,但它也会通过commandName属性构建针对某个模型对象的上下文信息。在其他的表单绑定标签中,会引用这个模型对象的属性。

在之前的代码中,我们将commandName属性设置为spitter。因此,在模型中必须要有一个key为spitter的对象,否则的话,表单不能正常渲染(会出现JSP错误)。这意味着我们需要修改一下SpitterController,以确保模型中存在以spitter为key的Spitter对象:

修改后的showRegistrationForm()方法中,新增了一个Spitter实例到模型中。模型中的key是根据对象类型推断得到的,也就是spitter,与我们所需要的完全一致。

回到这个表单中,前四个输入域将HTML <input>标签改成了<sf:input>。这个标签会渲染成一个HTML <input>标签,并且type属性将会设置为text。我们在这里设置了path属性,<input>标签的value属性值将会设置为模型对象中path属性所对应的值。例如,如果在模型中Spitter对象的firstName属性值为Jack,那么<sf:input path="firstName"/>所渲染的<input>标签中,会存在value="Jack"。

对于password输入域,我们使用<sf:password>来代替<sf:input>。<sf:password>与<sf:input>类似,但是它所渲染的HTML <input>标签中,会将type属性设置为password,这样当输入的时候,它的值不会直接明文显示。

为了帮助读者了解最终的HTML看起来是什么样子的,假设有个用户已经提交了表单,但值都是不合法的。校验失败后,用户会被重定向到注册表单,最终的HTML<form>元素如下所示:

值得注意的是,从Spring 3.1开始,<sf:input>标签能够允许我们指定type属性,这样的话,除了其他可选的类型外,还能指定HTML 5特定类型的文本域,如date、range和email。例如,我们可以按照如下的方式指定email域:

这样所渲染得到的HTML如下所示:

相对于标准的HTML标签,使用Spring的表单绑定标签能够带来一定的功能提升,在校验失败后,表单中会预先填充之前输入的值。但是,这依然没有告诉用户错在什么地方。为了指导用户矫正错误,我们需要使用<sf:errors>。

展现错误

如果存在校验错误的话,请求中会包含错误的详细信息,这些信息是与模型数据放到一起的。我们所需要做的就是到模型中将这些数据抽取出来,并展现给用户。<sf:errors>能够让这项任务变得很简单。

例如,让我们看一下将<sf:errors>用到registerForm.jsp中的代码片段:

尽管我只展现了将<sf:errors>用到First Name输入域的场景,但是它可以按照同样简单的方式用到注册表单的其他输入域中。在这里,它的path属性设置成了firstName,也就是指定了要显示Spitter模型对象中哪个属性的错误。如果firstName属性没有错误的话,那么<sf:errors>不会渲染任何内容。但如果有校验错误的话,那么它将会在一个HTML <span>标签中显示错误信息。

例如,如果用户提交字母“J”作为名字的话,那么如下的HTML片段就是针对First Name输入域所显示的内容:

现在,我们已经可以为用户展现错误信息,这样他们就能修正这些错误了。我们可以更进一步,修改错误的样式,使其更加突出显示。为了做到这一点,可以设置cssClass属性:

同样,简单起见,我只会展现如何为firstName输入域的<sf:errors>设置cssClass属性。你可以将其用到其他的输入域上。

现在errors的<span>会有一个值为error的class属性。剩下需要做的就是为这个类定义CSS样式。如下就是一个简单的CSS样式,它会将错误信息渲染为红色:

图6.2展现了这个表单此时在浏览器中的显式效果。

图6.2 在表单输入域的旁边展现校验错误信息

在输入域的旁边展现错误信息是一种很好的方式,这样能够引起用户的关注,提醒他们修正错误。但这样也会带来布局的问题。另外一种处理校验错误方式就是将所有的错误信息在同一个地方进行显示。为了做到这一点,我们可以移除每个输入域上的<sf:errors>元素,并将其放到表单的顶部,如下所示:

这个<sf:errors>与之前相比,值得注意的不同之处在于它的path被设置成了“*”。这是一个通配符选择器,会告诉<sf:errors>展现所有属性的所有错误。

同样需要注意的是,我们将element属性设置成了div。默认情况下,错误都会渲染在一个HTML <span>标签中,如果只显示一个错误的话,这是不错的选择。但是,如果要渲染所有输入域的错误的话,很可能要展现不止一个错误,这时候使用<span>标签(行内元素)就不合适了。像<div>这样的块级元素会更为合适。因此,我们可以将element属性设置为div,这样的话,错误就会渲染在一个<div>标签中。

像之前一样,cssClass属性被设置errors,这样我们就能为<div>设置样式。如下为<div>的CSS样式,它具有红色的边框和浅红色的背景:

现在,我们在表单的上方显示所有的错误,这样页面布局可能会更加容易一些。但是,我们还没有着重显示需要修正的输入域。通过为每个输入域设置cssErrorClass属性,这个问题很容易解决。我们也可以将每个label都替换为<sf: label>,并设置它的cssErrorClass属性。如下就是做完必要修改后的First Name输入域:

<sf: label>标签像其他的表单绑定标签一样,使用path来指定它属于模型对象中的哪个属性。在本例中,我们将其设置为firstName,因此它会绑定Spitter对象的firstName属性。假设没有校验错误的话,它将会渲染为如下的HTML<label>元素:

就其自身来说,设置<sf:label>的path属性并没有完成太多的功能。但是,我们还同时设置了cssErrorClass属性。如果它所绑定的属性有任何错误的话,在渲染得到的<label>元素中,class属性将会被设置为error,如下所示:

与之类似,<sf:input>标签的cssErrorClass属性被设置为error。如果有任何校验错误的话,在渲染得到的<input>标签中,class属性将会被设置为error。现在我们已经为文本标记和输入域设置了样式,这样当出现错误的时候,会将用户的注意力转移到此处。例如,如下的CSS会将文本标记渲染为红色,并将输入域设置为浅红色背景:

现在,我们有了很好的方式为用户展现错误信息。不过,我们还可以做另外一件事情,能够让这些错误信息更加易读。重新看一下Spitter类,我们可以在校验注解上设置message属性,使其引用对用户更为友好的信息,而这些信息可以定义在属性文件中:

对于上面每个域,我们都将其@Size注解的message设置为一个字符串,这个字符串是用大括号括起来的。如果没有大括号的话,message中的值将会作为展现给用户的错误信息。但是使用了大括号之后,我们使用的就是属性文件中的某一个属性,该属性包含了实际的信息。

接下来需要做的就是创建一个名为ValidationMessages.properties的文件,并将其放在根类路径之下:

ValidationMessages.properties文件中每条信息的key值对应于注解中message属性占位符的值。同时,最小和最大长度没有硬编码在ValidationMessages.properties文件中,在这个用户友好的信息中也有自己的占位符——{min}和{max}——它们会引用@Size注解上所设置的min和max属性。

当用户提交的注册表单校验失败的话,他们在浏览器中应该可以看到图6.3所示的界面。

将这些错误信息抽取到属性文件中还会带来一个好处,那就是我们可以通过创建地域相关的属性文件,为用户展现特定语言和地域的信息。例如,如果用户的浏览器设置成了西班牙语,那么就应该用西班牙语展现错误信息,我们需要创建一个名为Validation- Errors_es.properties的文件,内容如下:

图6.3 显示校验错误,其中这些对用户友好的信息是从属性文件中获取到的

我们可以按需创建任意数量的ValidationMessages.properties文件,使其涵盖我们想支持的所有语言和地域。

Spring通用的标签库

除了表单绑定标签库之外,Spring还提供了更为通用的JSP标签库。实际上,这个标签库是Spring中最早的标签库。这么多年来,它有所变化,但是在最早版本的Spring中,它就已经存在了。

要使用Spring通用的标签库,我们必须要在页面上对其进行声明:

与其他JSP标签库一样,prefix可以是任意你所喜欢的值。在这里,通用的做法是将这个标签库的前缀设置为spring。但是,我将其设置为“s”,因为它更加简洁,更易于阅读和输入。

标签库声明之后,我们就可以使用表6.3中的十个JSP标签了。

表6.3 Spring的JSP标签库中提供了多个便利的标签,还包括一些遗留的数据绑定标签

JSP标签

描  述

<s:bind>

将绑定属性的状态导出到一个名为status的页面作用域属性中,与<s:path>组合使用获取绑定属性的值

<s:escapeBody>

将标签体中的内容进行HTML和/或JavaScript转义

<s:hasBindErrors>

根据指定模型对象(在请求属性中)是否有绑定错误,有条件地渲染内容

<s:htmlEscape>

为当前页面设置默认的HTML转义值

<s:message>

根据给定的编码获取信息,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用var和scope属性实现)

<s:nestedPath>

设置嵌入式的path,用于<s:bind>之中

<s:theme>

根据给定的编码获取主题信息,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用var和scope属性实现)

<s:transform>

使用命令对象的属性编辑器转换命令对象中不包含的属性

<s:url>

创建相对于上下文的URL,支持URI模板变量以及HTML/XML/JavaScript转义。可以渲染URL(默认行为),也可以将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用var和scope属性实现)

<s:eval>

计算符合Spring表达式语言(Spring Expression Language,SpEL)语法的某个表达式的值,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用var和scope属性实现)

表6.3中的一些标签已经被Spring表单绑定标签库淘汰了。例如,<s:bind>标签就是Spring最初所提供的表单绑定标签,它比我们在前面所介绍的标签复杂得多。

因为这些标签库的行为比表单绑定标签少得多,所以我不会详细介绍每个标签,而是快速介绍几个最为有用的标签,其余的留给读者自行去学习和探索。(即便你们会用到它们,很可能也不会那么频繁。)

展现国际化信息

到现在为止,我们的JSP模板包含了很多硬编码的文本。这其实也算不上什么大问题,但是如果你要修改这些文本的话,就不那么容易了。而且,没有办法根据用户的语言设置国际化这些文本。

例如,考虑首页中的欢迎信息:

修改这个信息的唯一办法是打开home.jsp,然后对其进行变更。我觉得,这算不上什么大事。但是,应用中的文本散布到多个模板中,如果要大规模修改应用的信息时,你需要修改大量的JSP文件。

另外一个更为重要的问题在于,不管你选择什么样的欢迎信息,所有的用户都会看到同样的信息。Web是全球性的网络,你所构建的应用很可能会有全球化用户。因此,最好能够使用用户的语言与其进行交流,而不是只使用某一种语言。

对于渲染文本来说,是很好的方案,文本能够位于一个或多个属性文件中。借助<s:message>,我们可以将硬编码的欢迎信息替换为如下的形式:

按照这里的方式,<s:message>将会根据key为spittr.welcome的信息源来渲染文本。因此,如果我们希望<s:message>能够正常完成任务的话,就需要配置一个这样的信息源。

Spring有多个信息源的类,它们都实现了MessageSource接口。在这些类中,更为常见和有用的是ResourceBundleMessageSource。它会从一个属性文件中加载信息,这个属性文件的名称是根据基础名称(base name)衍生而来的。如下的@Bean方法配置了ResourceBundleMessageSource:

在这个bean声明中,核心在于设置basename属性。你可以将其设置为任意你喜欢的值,在这里,我将其设置为message。将其设置为message后,ResourceBundle-MessageSource就会试图在根路径的属性文件中解析信息,这些属性文件的名称是根据这个基础名称衍生得到的。

另外的可选方案是使用ReloadableResourceBundleMessageSource,它的工作方式与ResourceBundleMessageSource非常类似,但是它能够重新加载信息属性,而不必重新编译或重启应用。如下是配置ReloadableResourceBundle-MessageSource的样例:

这里的关键区别在于basename属性设置为在应用的外部查找(而不是像ResourceBundleMessageSource那样在类路径下查找)。basename属性可以设置为在类路径下(以“classpath:”作为前缀)、文件系统中(以“file:”作为前缀)或Web应用的根路径下(没有前缀)查找属性。在这里,我将其配置为在服务器文件系统的“/etc/spittr”目录下的属性文件中查找信息,并且基础的文件名为“message”。

现在,我们来创建这些属性文件。首先,创建默认的属性文件,名为messages. properties。它要么位于根类路径下(如果使用ResourceBundleMessageSource的话),要么位于pathname属性指定的路径下(如果使用ReloadableResourceBundle-MessageSource的话)。对spittr.welcome信息来讲,它需要如下的条目:

如果你不再创建其他信息文件的话,那么我们所做的事情就是将JSP中硬编码的信息抽取到了属性文件中,依然作为硬编码的信息。它能够让我们一站式地修改应用中的所有信息,但是它所完成的任务并不限于此。

我们已经具备了对信息进行国际化的重要组成部分。例如,如果你想要为语言设置为西班牙语的用户展现西班牙语的欢迎信息,那么需要创建另外一个名为messages_es. properties的属性文件,并包含如下的条目:

现在,我们已经完成了一件了不起的事情。我们的应用目前只是多了几个<s:message>标签以及语言相关的属性文件,还没有完全实现国际化!我将应用其他部分的国际化留给读者去完成。

创建URL

<s:url>是一个很小的标签。它主要的任务就是创建URL,然后将其赋值给一个变量或者渲染到响应中。它是JSTL中<c:url>标签的替代者,但是它具备几项特殊的技巧。

按照其最简单的形式,<s:url>会接受一个相对于Servlet上下文的URL,并在渲染的时候,预先添加上Servlet上下文路径。例如,考虑如下<s:url>的基本用法:

如果应用的Servlet上下文名为spittr,那么在响应中将会渲染如下的HTML:

这样,我们在创建URL的时候,就不必再担心Servlet上下文路径是什么了,<s:url>将会负责这件事。

另外,我们还可以使用<s:url>创建URL,并将其赋值给一个变量供模板在稍后使用:

默认情况下,URL是在页面作用域内创建的。但是通过设置scope属性,我们可以让<s:url>在应用作用域内、会话作用域内或请求作用域内创建URL:

如果希望在URL上添加参数的话,那么你可以使用<s:param>标签。比如,如下的<s:url>使用两个内嵌的<s:param>标签,来设置“/spittles”的max和count参数:

到目前为止,我们还没有看到<s:url>能够实现,而JSTL的<c:url>无法实现的功能。但是,如果我们需要创建带有路径(path)参数的URL该怎么办呢?我们该如何设置href属性,使其具有路径变量的占位符呢?

例如,假设我们需要为特定用户的基本信息页面创建一个URL。那没有问题,<s:param>标签可以承担此任:

当href属性中的占位符匹配<s:param>中所指定的参数时,这个参数将会插入到占位符的位置中。如果<s:param>参数无法匹配href中的任何占位符,那么这个参数将会作为查询参数。

<s:url>标签还可以解决URL的转义需求。例如,如果你希望将渲染得到的URL内容展现在Web页面上(而不是作为超链接),那么你应该要求<s:url>进行HTML转义,这需要将htmlEscape属性设置为true。例如,如下的<s:url>将会渲染HTML转义后的URL:

所渲染的URL结果如下所示:

另一方面,如果你希望在JavaScript代码中使用URL的话,那么应该将javaScript-Escape属性设置为true:

这会渲染如下的结果到响应之中:

既然提到了转义,有一个标签专门用来转义内容,而不是转义标签。接下来,让我们看一下。

转义内容

<s:escapeBody>标签是一个通用的转义标签。它会渲染标签体中内嵌的内容,并且在必要的时候进行转义。

例如,假设你希望在页面上展现一个HTML代码片段。为了正确显示,我们需要将“<”和“>”字符替换为“<”和“>”,否则的话,浏览器将会像解析页面上其他HTML那样解析这段HTML内容。

当然,没有人禁止我们手动将其转义为“<”和“>”,但是这很烦琐,并且代码难以阅读。我们可以使用<s:escapeBody>,并让Spring完成这项任务:

它将会在响应体中渲染成如下的内容:

虽然转义后的格式看起来很难读,但浏览器会很乐意将其转换为未转义的HTML,也就是我们希望用户能够看到的样子。

通过设置javaScriptEscape属性,<s:escapeBody>标签还支持JavaScript转义:

<s:escapeBody>只完成一件事,并且完成得非常好。与<s:url>不同,它只会渲染内容,并不能将内容设置为变量。

现在,我们已经看到了如何使用JSP来定义Spring视图,现在让我们考虑一下如何使其在审美上更加有吸引力。我们可以在页面上增加一些通用的元素,比如添加包含站点Logo的头部、使用样式并在底部展现版权信息。我们不会在Spittr应用中的每个JSP都进行这样的修改,而是借助Apache Tiles来为模板实现一些通用且可重用的布局。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文