本文基于《Spring实战(第4版)》所写。
理解视图解析
Spring MVC定义了一个名为ViewResolver的接口,它大致如下所示:
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
当给resolveViewName()方法传入一个视图名和Locale对象时,它会返回一个View实例。View是另外一个接口,如下所示:
public interface View {
String getContentType();
void render(Map model,
HttpServletRequest request,
HttpServletResponse response) throws Exception;
}
View接口的任务就是接受模型以及Servlet的request和response对象,并将输出结果渲染到response中。
尽管我们可以编写ViewResolver和View的实现,在有些特定的场景下,这样做也是有必要的,但一般来讲,我们并不需要关心这些接口。Spring提供了多个内置的实现。如下表所示,Spring自带了13个视图解析器,能够将逻辑视图名转换为物理实现。
视图解析器 | 描述 |
---|---|
BeanNameViewResolver | 将视图解析为Spring应用上下文的bean,其中bean的ID与视图的名字相同 |
ContentNegotiatingViewResolver | 通过考虑客户端需要的内容类型来解析视图,委托给另外一个能够产生对应内容类型的视图解析器 |
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支持表6的所有视图解析器。Spring 3.1支持除Tiles 3 TilesViewResolver之外的所有视图解析器。
创建JSP视图
Spring提供了两种支持JSP视图的方式:
- InternalResourceViewResolver会将视图名解析为JSP文件。另外,如果在你的JSP页面中使用了JSP标准标签库(JavaServer Pages Standard Tag Library, JSTL)的话,InternalResourceViewResolver能够将视图名解析为JstlView形式的JSP文件,从而将JSTL本地化和资源bundle变量暴露给JSTL的格式化(formatting)和信息(message) 标签。
- Spring提供了两个JSP标签库,一个用于表单到模型的绑定,另一个提供了通用的工具类特性。
不管使用JSTL,还是准备使用Spring的JSP标签库,配置解析JSP的视图解析器都是非常重要的。尽管Spring还有其他的几个视图解析器都能将视图名映射为JSP文件,但就这项任务来讲,InternalResourceViewResolver是最简单和最常用的视图解析器。
配置适用于JSP的视图解析器
InternalResourceViewResolver遵循一种约定,会在视图名上添加前缀和后缀,进而确定一个Web应用中视图资源的物理路径。
通用的实践是将JSP文件放到Web应用的WEB-INF目录下,防止对它的直接访问。假设逻辑视图名为home,那么可以确定物理视图的路径就是逻辑视图名home再加上“/WEB-INF/views/”前缀和“.jsp”后缀。
当使用@Bean注解的时候,我们可以按照如下的方法配置InternalResourceViewResolver,使其在解析视图时,遵循上述的约定。
@Bean
public ViewResolver viewResolver(){ // 配置jsp视图解析器
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
作为替代方案,如果更喜欢使用基于XML的Spring配置,那么可以按照如下的方式配置InternalResourceViewResolver
InternalResourceViewResolver配置就绪之后,它就会将逻辑视图名解析为JSP文件,如下所示:
- home将会解析为“/WEB-INF/views/home.jsp”
- productList将会解析为“/WEB-INF/views/productList.jsp”
- books/detail将会解析为“/WEB-INF/views/books/detail.jsp”
重点看一下最后一个样例。当逻辑视图名中包含斜线时,这个斜线也会带到资源的路径名中。因此,它会对应到prefix属性所引用目录的子目录下的JSP文件。这样的话,我们就可以很方便地将视图模板组织为层级目录,而不是将它们都放到同一个目录之中。
解析JSTL视图
如果JSP使用JSTL标签来处理格式化和信息的话,那么我们会希望InternalResourceViewResolver将视图解析为JstlView。
JSTL的格式化标签需要一个Locale对象,以便于恰当地格式化地域相关的值,如日期和货币。信息标签可以借助Spring的信息资源和Locale,从而选择适当的信息渲染到HTML之中。通过解析JstlView,JSTL能够获得Locale对象以及Spring中配置的信息资源。
如果想让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完成这一个任务:
不管使用Java配置还是使用XML,都能确保JSTL的格式化和信息标签能够获得Locale对象以及Spring中配置的信息资源。
使用Spring的JSP库
Spring提供了两个JSP标签库,用来帮助定义Spring MVC Web的视图。其中一个标签库会用来渲染HTML表单便签,这些标签可以绑定model中的某个属性。另外一个标签库包含了一些工具类标签,我们随时都可以非常便利地使用它们。
我们将会看到如何将Spittr应用的注册表单绑定到模型上,这样表单就可以预先填充值,并且在表单提交失败后,能够展现校验错误。
将表单绑定到模型上
Spring的表单绑定JSP标签库包含了14个标签,它们中的大多数都用来渲染HTML中的表单标签。但是,它们与原生HTML标签的区别在于它们会绑定模型中的一个对象,能够根据模型中对象的属性填充值。标签库中还包含了一个为用户展示错误的标签,它会将错误信息渲染到最终的HTML之中。
为了使用表单绑定库,需要在JSP页面中对其进行声明:
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>
需要注意,我们将前缀指定为“sf”,但通常也可能使用“form”前缀,可以自定义前缀。
在声明完表单绑定标签库之后,你就可以使用14个相关的便签了。如下表
JSP标签 | 描述 |
---|---|
渲染成一个HTML 标签,其中type属性设置为checkbox | |
渲染成多个HTML 标签,其中type属性设置为checkbox | |
在一个HTML 中渲染输入域的错误 | |
渲染成一个HTML | |
渲染成一个HTML 标签,其中type属性设置为hidden | |
渲染成一个HTML 标签,其中type属性设置为text | |
渲染成一个HTML | |
渲染成一个HTML | |
按照绑定的集合、数组或Map,渲染成一个HTML | |
渲染成一个标签,其中type属性设置为password | |
渲染成一个标签,其中type属性设置为radio | |
渲染为一个HTML | |
渲染为一个HTML |
我们在Spittr的样例中,在注册JSP中可以使用
First Name:
Last Name:
Email:
Username:
Password:
在之前的代码中,我们将commandName属性设置为spitter。因此,在模型中必须要有一个key为spitter的对象,否则的话,表单不能正常渲染(会出现JSP错误)。这意味着我们需要修改一下SpitterController,以确保模型中存在以spitter为key的Spitter对象:
@RequestMapping(value="/register", method=GET)
public String showRegistrationForm(Model model) {
model.addAttribute(new Spitter());
return "registerForm";
}
修改后,模型中的key根据对象类型推断得出spitter就是新增的Spitter实例。
回到这个表单中,前四个输入域将HTML 标签改成了
对于password输入域,我们使用
值得注意的是,从Spring 3.1开始,
Email:
为了指导用户矫正错误,我们需要使用
如果存在校验错误的话,请求中会包含错误的详细信息,这些信息是与模型数据放到一起的。我们所需要做的就是到模型中将这些数据抽取出来,并展现给用户。
First Name:
...
尽管值展示了将
现在,我们已经可以为用户展现错误信息,这样他们就能修正这些错误了。我们还可以修改错误的样式,使其更加突出显示。为了做到这一点,可以设置cssClass属性:
First Name:
...
定义这个css样式
span.error {
color: red;
}
展示显示结果
除了这种方式,还有另一种处理校验错误方式就是将所有的错误信息在同一个地方进行显示。为了做到这一点,我们可以移除每个输入域上的
...
跟之前相比,值得注意的不同之处在于它的path被设置成了“*”。这是一个通配符选择器,会告诉
同样需要注意的是,我们将element属性设置成了div。默认情况下,错误都会渲染在HTML 标签中,如果只显示一个错误的话,这是不错的选择。但是,如果要渲染所有输入域的错误的话,很可能要展现不止一个错误,这时候使用标签(行内元素)就不合适了。像 像之前一样,cssClass属性被设置errors,这样我们就能为 现在,我们在表单的上方显示所有的错误,这样页面布局可能会更加容易一些。但是,我们还没有着重显示需要修正的输入域。通过为每个输入域设置cssErrorClass属性,这个问题很容易解决。我们也可以将每个label都替换为 就其自身来说,设置 与之类似, 为了让这些错误信息更加易读,我们重新改造Spitter类 对于上面每个域,我们都将其@Size注解的messgae设置为一个字符串,这个字符串是用大括号括起来的。如果没有大括号的话,message中的值将会作为展现给用户的错误信息。但是使用了大括号之后,我们使用的就是属性文件中的某一属性,该属性包含了实际的信息。 接下来需要做的就是创建一个名为ValidationMessage.properties的文件,并将其放在根类路径下: ValidationMessage.properties文件中每天信息的key值对应于注解中message属性占位符的值。同时,最小和最大长度以占位符的方式({min}和{max})保存文件中,它们会引用@Size注解上所设置的min和max属性。 当用户提交的注册表单校验失败的话,他们在浏览器中应该可以看到如下界面。 我们可以按需创建任意数量的ValidationMessage.properties文件,使其涵盖我们想支持的所有语言和地域。 Spring通用的标签库 除了表单绑定标签库之外,Spring还提供了更为通用的JSP标签库。 要使用Spring通用的标签库,我们必须要在页面上对其进行声明: 标签库声明之后,我们就可以使用下表的十个JSP标签了。 展现国际化信息 如果要修改JSP模板中的文本,就不那么容易,而且,没有办法根据用户的语言设置国际化这些文本。 如果想把其中的文本做成国际化的版本,对于渲染文本来说,是很好的方案,文本能够位于一个或多个属性文件中。借助 按照这里的方式, Spring有多个信息源的类,它们都实现了MessageSource接口。在这些类中,更为常见和有用的是ResourceBundleMessageSource。它会从一个属性文件中加载信息,这个属性文件的名称是根据基础名称(base name)衍生而来的。如下的@Bean方法配置了ResourceBundleMessageSource: 在这个bean声明中,核心在于设置basename属性。你可以将其设置为任意你喜欢的值,在这里,我将其设置为message。将其设置为message后,ResourceBundleMessageSource就会试图在根路径的属性文件中解析信息,这些属性文件的名称是根据这个基础名称衍生得到的。 另外的可选方案是使用ReloadableResourceBundleMessageSource,它的工作方式与ResourceBundleMessageSource非常类似,但是它能够重新加载信息属性,而不必重新编译或重启应用。如下是配置ReloadableResourceBundleMessageSource的样例: 这里的关键区别在于basename属性设置为在应用的外部查找。basename属性可以设置为类路径下(以“classpath:”作为前缀)、文件系统中(以“file:”作为前缀)或Web应用的根路径下(没有前缀)查找属性。 现在,我们来创建这些属性文件。首先创建默认的属性文件,名为message.properties。它要们位于根类路径下(如果使用ResourceBundleMessageSource的话),要么位于basename属性指定的路径下(如果使用ReloadableResourceBundleMessageSource的话)。对spittr.welcome信息来讲,它需要如下的条目: 我们已经具备了对信息进行国际化的重要组成部分。例如,如果想要为语言设置为西班牙语的用户展示西班牙语的欢迎信息,那么需要创建另外一个名为message_es.properties的属性文件,并包含如下的条目: 创建URL 如果应用的Servlet上下文名为spittr,那么在响应中将会渲染如下的HTML: 这样,我们在创建URL的时候,就不必再担心Servlet上下文路径是什么了, 另外,我们还可以使用 默认情况下,URL是在页面作用域内创建的。但是通过设置scope属性,我们可以让 如果希望在URL上添加参数的话,那么你可以使用 如果我们需要创建带有路径(path)参数的URL,我们该如何设置》 例如,假设我们需要为特定用户的基本信息页面创建一个URL。那没有问题, 当href属性中的占位符匹配 所渲染的URL结果如下所示: 另一方面,如果你希望在JavaScript代码中使用URL的话,那么应该将javaScriptEscape属性设置为true: 这会渲染如下的结果到响应之中: 转义内容 例如,假设你希望在页面上展现一个HTML代码片段。为了正确显示,我们需要将“<”和“>”字符替换为“<”和“>”,否则的话,浏览器将会像解析页面上其他HTML那样解析这段HTML内容。 当然,没有人禁止我们手动将其转义为“<”和“>”,但是这很繁琐,并且代码难以阅读。我们可以使用 它将会在响应体中渲染成如下的内容: 虽然转义后的格式看起来很难读,但浏览器可以将其转换为未转义的HTML。 通过设置javaScriptEscape属性,div.errors {
background-color: #ffcccc;
border: 2px solid red;
}
...
label.error {
color: red;
}
input.error {
background-color: #ffcccc;
}
@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;
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.
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
JSP标签
描述
将绑定属性的状态导出到一个名为status的页面作用域属性中,与
将标签体中的内容进行HTML和/或JavaScript转义
根据指定模型对象(在请求属性中)是否有绑定错误,有条件地渲染内容
为当前页面设置默认的HTML转义值
根据给定的编码获取信息,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域应用作用域的变量(通过使用var和scope属性实现)
设置嵌入式的path,用于
根据给定的编码获取主题信息,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域应用作用域的变量(通过使用var和scope属性实现)
使用命名对象的属性编辑器转换命令对象中不包含的属性
创建相对于上下文的URL,支持URI模板变量以及HTML/XML/JavaScript转义。可以渲染URL(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域应用作用域的变量(通过使用var和scope属性实现)
计算符合Spring表达式语言(Spring Expression Language SpEL)语法的某个表达式的值,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域应用作用域的变量(通过使用var和scope属性实现)
例如:Welcome to Spitter!
@Bean
public MessageSource messageSource(){
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("message");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
@Bean
public MessageSource messageSource(){
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("file:///etc/spittr/messages");
messageSource.setCacheSeconds(10);
return messageSource;
}
spittr.welcome=Welcome to Spittr!
spittr.welcome=Bienvenidos a Spittr!
">Register
Register
/spitter/spittles?max=60&count=20
Hello
<h1>Hello</h1>
Hello