一、理解视图解析
在之前的讲解中,我们使用名为InternalResourceViewResolver
的视图解析器。在它的配置中,为了得到视图的名字,会使用“/WEB-INF/views/”
前缀和“.jsp”
后缀,从而确定来渲染模型的JSP
文件的物理位置。现在回过头来看看视图解析的基础知识以及Spring
提供的其他视图解析器。
Spring MVC
定义了一个名为ViewResolver
的接口:
public interface ViewResolver{
View resolveViewName(String viewName, Locale locale) throw Exception;
}
此方法传入一个视图名和Locale
对象时,它会返回一个View
实例。View
是另外一个接口:
public interface View{
String getContentType();
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throw Exception;
}
说明:View
接口的任务就是接受模型以及Servlet
的request
和response
对象,并将输出结果渲染到response
中。看起来非常简单,我们只需要编写上述两个接口的实现即可,有时候也需要这样做,但是一般来讲,并不需要,Spring
提供了多个内置的实现。
Spring
自带了十三个视图解析器,能够将逻辑视图名转换为物理实现。
视图解析器 | 描述 |
---|---|
BeanNameViewResolver |
将视图解析为Spring 应用上下文中的bean ,其中bean 的ID 与视图的名字相同 |
ContentNegotiatingViewResovler |
通过考虑客户端需要的内容类型来解析视图,委托给另外一个能够产生对应内容类型的视图解析器 |
FreeMarkerViewResolver |
将视图解析为FreeMarker 模版 |
InternalResourceViewResolver |
将视图解析为Web 应用的内部资源(一般为JSP ) |
JasperReportsViewResolver |
将视图解析为JasperReports 定义 |
ResourceBundleViewResolver |
将视图解析为资源bundle (一般为属性文件) |
TilesViewResolver |
将视图解析为Apache Tile 定义,其中tile ID 与视图名称相同,注意有两个不同的TilesViewResolver 实现,分别对应于Tiles 2.0 和Tiles 3.0
|
UrlBasedViewResolver |
直接根据视图的名称解析视图,视图的名称会匹配一个物理视图的定义 |
VelocityLayoutViewResolver |
将视图解析为Velocity 布局,从不同的Velocity 模版中组合页面 |
VelocityViewResolver |
将视图解析为Velocity 模版 |
XmlViewResolver |
将视图解析为特定的XML 文件中的bean 定义,类似于BeanNameViewResolver
|
XsltViewResolver |
将视图解析为XSLT 转换后的结果 |
说明:Spring 4
和Spring 3.2
支持表中所有的视图解析器。Spring 3.1
支持除了Tiles 3 TilesViewResolver
之外的所有视图解析器。这里InternalResourceViewResolver
一般会用于JSP
,TilesViewResolver
用于Apache Tiles
视图,而FreeMarkerViewResolver
和VelocityViewResolver
分别对应FreeMarker
和Velocity
模版视图。
二、创建JSP视图
Spring
提供了两种支持JSP
视图的方式:
InternalResourceViewResolver
会将视图名解析为JSP
文件。另外,如果在JSP
页面中使用了JSTL
,此解析器会将视图名解析为JstlView
形式的JSP
文件,从而将JSTL
本地化好资源bundle
变量暴露给JSTL
的格式化和信息标签。Spring
提供了两个JSP
标签库,一个用于表单到模型的绑定,另一个提供了通用的工具类特性。
2.1 配置适用于JSP的视图解析器
有一些视图解析器,如ResourceBundleViewResovler
会直接将逻辑视图名映射为特定的View
接口实现,而InternalResourceViewResolver
所采取的方式并不那么直接。它遵循一种约定,会在视图名上添加前缀和后缀,进而确定一个Web
应用中视图资源的物理路径(前面一节已经说明,这里不再细说)。
在之前的13
讲中的WebConfig
配置类中是这样配置视图解析器的:
@Bean
public ViewResolver viewResolver() {
//配置JSP视图解析器
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
说明:当然我们也可以使用XML
进行配置:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/views/" p:suffix=".jsp" />
说明:解析器配置好之后,它就会将逻辑视图名解析为JSP
文件,如下所示:
-
home
将会解析为“/WEB-INF/views/home.jsp”
-
productList
将会解析为“/WEB-INF/views/productList.jsp”
-
books/detail
将会解析为“/WEB-INF/views/books/detail.jsp”
2.1.1 解析JSTL视图
上述对视图解析器的配置都很基础。如果一些JSP
使用JSTL
标签来处理格式化和信息的话,那么我们会希望InternalResourceViewResolver
将视图解析为JstlView
。JSTL
的格式化标签需要一个Locale
对象,以便于恰当地格式化地域相关的值,如日期和货币。信息标签可以借助Spring
的信息资源和Locale
,从而选择适当的信息渲染到HTML
中。
如果想让InternalResourceViewResolver
将视图解析为JstlView
,而不是InternalResourceView
的话,只需要设置它的viewClass
属性即可:
@Bean
public ViewResolver viewResolver() {
//配置JSP视图解析器
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(org.springframework.web.servlet.view.JstlView.class);
return resolver;
}
同样,我们也可以使用XML
完成这一项内容:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/views/" p:suffix=".jsp" p:viewClass="org.springframework.web.servlet.view.JstlView"/>
2.2 使用 Spring 的 JSP 库
当为JSP
添加功能时,标签库是一种很强大的方式,能够避免在脚本块中直接编写Java
代码。Spring
提供了两个JSP
标签库,用来帮助定义Spring MVC Web
的视图。其中一个标签库会用来渲染HTML
表单标签,这些标签可以绑定model
中的某个属性。另外一个标签库包含了一些工具类标签。
2.2.1 将表单绑定到模型上
Spring
的表单绑定JSP
标签库包含了十四个标签,它们中的大多数都用来渲染HTML
中的表单标签。但是,它们与原生的HTML
标签的区别在于它们会绑定模型中的一个对象,能够根据模型中对象的属性填充值。还包含了一个为用户展现错误的标签,它会将错误信息渲染到最终的HTML
之中。
为了使用表单绑定,需要在JSP
页面中对其进行声明:
<%@ talib uri="http://www.springframework.org/tags/form" prefix="sf" %>
借助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> 标签 |
下面利用上述标签构建注册表单:
<sf:form method="POST" commandName="spitter">
First Name: <sf:input path="firstName" /><br/>
Last Name: <sf:input path="lastName" /><br/>
Email: <sf:input path="email" /><br/>
Username: <sf:input path="username" /><br/>
Password: <sf:input path="password" /><br/>
<input type="submit" value="Register" />
</sf:form>
说明:<sf:form>
会渲染一个HTML <form>
标签,但它也会通过commandName
属性构建针对某个对象的上下文信息。在其他的表单绑定标签中,会引用这个模型对象的属性。这里我们将commandName
属性设置为spitter
,因此,在模型中必须要有一个key
为spitter
的对象,否则的话,表单不能正常渲染。下面看如何确保模型中存在key
为spitter
的对象(在控制器中设置):
@RequestMapping(value="/register", method=RequestMethod.GET)
public String showRegistrationForm(Model model) {
model.addAttribute(new Spitter());
return "registerForm";
}
说明:修改后,模型中的key
是根据对象类型推断得到的,也就是spitter
。
使用Spring
标签库后最终的<from>
元素如下:
<form id="spitter" action="/spitter/spitter/register" method="POST">
First Name:<input id="firstName" name="firstName" type="text" value="Jack" />
...
</form>
可以看到会自动帮我们填写相关属性。而从Spring 3.1
开始,<sf:input>
标签能够允许我们指定type
属性,还能指定HTML 5
特定类型的文本域,如date、range
和email
。
Email:<sf:input path="email" type="email" />
这样所渲染得到的HTML
如下:
Email:<input id="email" name="email" type="email" value="jack"/>
这里value
指定默认值。
2.2.2 展现错误
如果存放在校验错误的话,请求中会包含错误的详细信息,这些信息是与模型数据放在一起的。这里使用<sf:errors>
标签让错误抽取出来显示。如将此标签用到registerForm.jsp
中的代码片段:
<sf:form method="POST" commandName="spitter">
First Name: <sf:input path="firstName" />
<sf:errors path="firstName" /><br />
...
</sf:form>
此时如果有校验错误的话,那么它将会在一个HTML <span>
标签中显示错误信息。此时得到的渲染的HTML
为:
<form id="spitter" action="/spitter/spitter/register" method="POST">
First Name:<input id="firstName" name="firstName" type="text" value="Jack" />
<span id="firstName.errors">size must be between 2 and 30</span>
</form>
可以更近一步,修改错误的样式,使其更加突出显示。为了做到这一点,可以设置cssClass
属性:
<sf:form method="POST" commandName="spitter">
First Name: <sf:input path="firstName" />
<sf:errors path="firstName" cssClass="error" /><br />
...
</sf:form>
这样error
的<span>
会有一个值为error
的class
属性,于是可以为这个类定义CSS
样式:
span.error{
color: red;
}
这样相关提示或错误信息都在每个字段的后面显示,但这样会带来布局的问题。另一种处理校验错误方式就是将所有的错误信息在同一个地方进行显示。我们可以移除每个输入域上的<sf:errors>
元素,并将其放到表单的顶部:
<sf:form method="POST" commandName="spitter">
<sf:errors path="*" element="div" cssClass="error" />
...
</sf:form>
这里和之前不同在于path
被设置成了“*”
,这表示<sf:errors>
展现所有属性的所有错误。同时,将element
属性设置成了div
,默认情况下是span
标签。因为需要一下子显示多个错误信息,使用span
标签(行内元素)就不合适了。此时设置样式如下:
div.errors{
background-color: #ffcccc;
border: 2px solid red;
}
现在,我们在表单的上方显示所有的错误,这样页面布局可能会更加容易一些。但是,我们还没有着重显示需要修正的输入域。通过为每个输入域设置cssErrorClass
属性,这里可以将每个label
都替换为<sf:label>
,并设置它的cssErrorClass
属性:
<sf:form method="POST" commandName="spitter">
<sf:errors path="*" element="div" cssClass="error" />
<sf:label path="firstName" cssErrorClass="error">
First Name
</sf:label>:
<sf:input path="firstName" cssErrorClass="error"/><br/>
...
</sf:form>
说明:<sf:label>
标签像其他的表单绑定标签一样,使用path
来指定它属于模型对象中的哪个属性。于是它会渲染为如下的HTML <label>
元素:
<label for="firstName">First Name</label>
但是设置<sf:label>
的path
属性并没有完成太多的功能,但是我们还设置了cssErrorClass
属性。于是渲染如下:
<label for="firstName" class="error">First Name</label>
现在,我们还可以让错误信息更加易读,在Spitter
类可以加上message
属性:
@NotNull
@Size(min=5, max=16, message="{username.size}")
private String username;
@NotNull
@Size(min=5, max=25, message="{password.size}")
private String password;
@NotNull
@Size(min=2, max=30, message="{firstName.size}"
private String firstName;
@NotNull
@Size(min=2, max=30, message="{lastName.size}")
private String lastName;
@NotNull
@Email(message="{email.valid}")
private String email;
接下来需要做的就是创建一个名为ValidationMessages.properties
的文件,并将其放在根类路径之下:
firstName.size=First name must be between {min} and {max} characters long.
lastName.size=Last name must be between {min} and {max} characters long.
username.size=Username must be between {min} and {max} characters long.
password.size=Password must be between {min} and {max} characters long.
email.valid=The email address must be valid.
这里的占位符{min}、{max}
会引用@Size
注解上所设置的min
和max
属性。