一、理解视图解析
在之前的讲解中,我们使用名为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属性。