本章内容:
将模型数据渲染为HTML
使用JSP视图
通过tiles定义视图布局
使用Thymeleaf视图
在《Spring实战》的书中是包括6.3 使用Apache Tiles视图定义布局和6.4 使用Thymeleaf这两个小节的,但是考虑到平时开发使用的比较少,就不进行介绍了,感兴趣的小伙伴们可以自行学习。
上一章主要关注于如何编写处理Web请求的控制器。我们也创建了一些简单的视图,用来渲染控制器产生的模型数据,但我们并没有花太多时间讨论视图,也没有讨论控制器完成请求到结果渲染到用户的浏览器中的这段时间内到底发生了什么,而这正是本章的主要内容。
6.1 理解视图解析
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个视图解析器,能够将逻辑视图名转换为物理实现。
Spring 4和Spring 3.2支持表6的所有视图解析器。Spring 3.1支持除Tiles 3 TilesViewResolver之外的所有视图解析器。
6.2 创建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”后缀。
InternalResourceViewResolver解析视图时,会在视图名上添加前缀和后缀
当使用@Bean注解的时候,我们可以按照如下的方法配置InternalResourceViewResolver,使其在解析视图时,遵循上述的约定。
@Beanpublic 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属性即可:
@Beanpublic 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页面中对其进行声明:
需要注意,我们将前缀指定为“sf”,但通常也可能使用“form”前缀,可以自定义前缀。
在声明完表单绑定标签库之后,你就可以使用14个相关的便签了。如下表
我们在Spittr的样例中,在注册JSP中可以使用、
和
WW
First Name: Last Name: Email: Username: Password:
会渲染一个HTML
标签,但它也会通过commandName属性构建针对某个模型对象的上下文信息。在其他的表单绑定标签中,会引用这个模型对象的属性。
在之前的代码中,我们将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标签改成了
。这个标签会渲染成一个HTML
标签,并且type属性将会设置为text。我们在这里设置了path属性,
标签的value属性值将会设置为模型对象中path属性所对应的值。
对于password输入域,我们使用来代替
。
与
类似,但是它所渲染的HTML
标签中,会将type属性为password,这样当输入的时候,它的值不会直接明文显示。
值得注意的是,从Spring 3.1开始,标签能够允许我们指定type属性,这样的话,除了其他可选的类型外,还能指定HTML5特定类型的文本域,如date、range和email。
Email:
为了指导用户矫正错误,我们需要使用
如果存在校验错误的话,请求中会包含错误的详细信息,这些信息是与模型数据放到一起的。我们所需要做的就是到模型中将这些数据抽取出来,并展现给用户。
First Name: ...
尽管值展示了将用到First Name输入域的场景,但是它可以按照同样简单的方式用到注册表单的其他输入域中。在这里,它的path属性设置成了firstName,也就是指定了要显示Spitter模型对象中哪个属性的错误。如果firstName属性没有错误的话,那么
不会渲染任何内容。但如果有校验错误的话,那么将会在HTML
标签中显示错误信息。
现在,我们已经可以为用户展现错误信息,这样他们就能修正这些错误了。我们还可以修改错误的样式,使其更加突出显示。为了做到这一点,可以设置cssClass属性:
First Name: ...
定义这个css样式
span.error {color: red;}
展示显示结果
在表单输入域的旁边展现校验错误信息
除了这种方式,还有另一种处理校验错误方式就是将所有的错误信息在同一个地方进行显示。为了做到这一点,我们可以移除每个输入域上的元素,并将其放到表单的顶部
...
跟之前相比,值得注意的不同之处在于它的path被设置成了“*”。这是一个通配符选择器,会告诉展现所有属性的所有错误。
同样需要注意的是,我们将element属性设置成了div。默认情况下,错误都会渲染在HTML 标签中,如果只显示一个错误的话,这是不错的选择。但是,如果要渲染所有输入域的错误的话,很可能要展现不止一个错误,这时候使用标签(行内元素)就不合适了。像
这样的块级元素会更为合适。因此,我们可以将element属性设置成了div。
像之前一样,cssClass属性被设置errors,这样我们就能为
设置样式了。
div.errors {background-color: #ffcccc;border: 2px solid red;}
现在,我们在表单的上方显示所有的错误,这样页面布局可能会更加容易一些。但是,我们还没有着重显示需要修正的输入域。通过为每个输入域设置cssErrorClass属性,这个问题很容易解决。我们也可以将每个label都替换为
,并设置它的cssErrorClass属性。如下就是做完必要修改后的FirstName输入域:
First Name: ...
标签像其他的表单绑定标签一样,使用path来指定它属于模型对象中的哪个属性。在本例中,我们将其设置为firstName,因此它会绑定Spitter对象的firstName属性。假设没有校验错误的话,它将会渲染为如下的HTML
元素:
First Name
就其自身来说,设置
的path属性并没有完成太多的功能,但是,我们还同时设置了cssErrorClass属性。如果它所绑定的属性有任何错误的话,在渲染得到的
元素中,class属性将会被设置为 error,如下所示:
First Name
与之类似,
标签的cssErrorClass属性被设置为error,如果有任何校验错误,class属性将会被设置为error。下面设置css
label.error {color: red;}input.error {background-color: #ffcccc;}
为了让这些错误信息更加易读,我们重新改造Spitter类
@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;
对于上面每个域,我们都将其@Size注解的messgae设置为一个字符串,这个字符串是用大括号括起来的。如果没有大括号的话,message中的值将会作为展现给用户的错误信息。但是使用了大括号之后,我们使用的就是属性文件中的某一属性,该属性包含了实际的信息。
接下来需要做的就是创建一个名为ValidationMessage.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.
ValidationMessage.properties文件中每天信息的key值对应于注解中message属性占位符的值。同时,最小和最大长度以占位符的方式({min}和{max})保存文件中,它们会引用@Size注解上所设置的min和max属性。
当用户提交的注册表单校验失败的话,他们在浏览器中应该可以看到如下界面。
显示校验错误,其中这些对用户友好的信息是从属性文件中获取到的
我们可以按需创建任意数量的ValidationMessage.properties文件,使其涵盖我们想支持的所有语言和地域。
Spring通用的标签库
除了表单绑定标签库之外,Spring还提供了更为通用的JSP标签库。
要使用Spring通用的标签库,我们必须要在页面上对其进行声明:
标签库声明之后,我们就可以使用下表的十个JSP标签了。
展现国际化信息
如果要修改JSP模板中的文本,就不那么容易,而且,没有办法根据用户的语言设置国际化这些文本。
例如:
Welcome to Spitter!
Welcome to Spitter!
如果想把其中的文本做成国际化的版本,对于渲染文本来说,是很好的方案,文本能够位于一个或多个属性文件中。借助
,我们可以将硬编码的欢迎信息替换为如下的形式:
按照这里的方式,
将会根据key为spitter.welcome的信息源来渲染文本。
Spring有多个信息源的类,它们都实现了MessageSource接口。在这些类中,更为常见和有用的是ResourceBundleMessageSource。它会从一个属性文件中加载信息,这个属性文件的名称是根据基础名称(base name)衍生而来的。如下的@Bean方法配置了ResourceBundleMessageSource:
@Beanpublic MessageSource messageSource(){ ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("message"); messageSource.setDefaultEncoding("UTF-8");return messageSource;}
在这个bean声明中,核心在于设置basename属性。你可以将其设置为任意你喜欢的值,在这里,我将其设置为message。将其设置为message后,ResourceBundleMessageSource就会试图在根路径的属性文件中解析信息,这些属性文件的名称是根据这个基础名称衍生得到的。
另外的可选方案是使用ReloadableResourceBundleMessageSource,它的工作方式与ResourceBundleMessageSource非常类似,但是它能够重新加载信息属性,而不必重新编译或重启应用。如下是配置ReloadableResourceBundleMessageSource的样例:
@Beanpublic MessageSource messageSource(){ ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setBasename("file:///etc/spittr/messages"); messageSource.setCacheSeconds(10);return messageSource;}
这里的关键区别在于basename属性设置为在应用的外部查找。basename属性可以设置为类路径下(以“classpath:”作为前缀)、文件系统中(以“file:”作为前缀)或Web应用的根路径下(没有前缀)查找属性。
现在,我们来创建这些属性文件。首先创建默认的属性文件,名为message.properties。它要们位于根类路径下(如果使用ResourceBundleMessageSource的话),要么位于basename属性指定的路径下(如果使用ReloadableResourceBundleMessageSource的话)。对spittr.welcome信息来讲,它需要如下的条目:
spittr.welcome=Welcome to Spittr!
我们已经具备了对信息进行国际化的重要组成部分。例如,如果想要为语言设置为西班牙语的用户展示西班牙语的欢迎信息,那么需要创建另外一个名为message_es.properties的属性文件,并包含如下的条目:
spittr.welcome=Bienvenidos a Spittr!
创建URL
的主要任务就是创建URL,然后将其赋值给一个变量或者渲染到响应中。它是JSTL中
标签的替代者,但是它具备几项特殊的技巧。
会接受一个相对Servlet上下文的URL,并在渲染的时候,预先添加上Servlet上下文路径。例如,考虑如下
的基本用法:
">Register
如果应用的Servlet上下文名为spittr,那么在响应中将会渲染如下的HTML:
Register
这样,我们在创建URL的时候,就不必再担心Servlet上下文路径是什么了,
将会负责这件事。
另外,我们还可以使用
创建URL,并将其赋值给一个变量供模板在稍后使用:
Register
默认情况下,URL是在页面作用域内创建的。但是通过设置scope属性,我们可以让
在应用作用域内、会话作用域内或请求作用域内创建URL:
如果希望在URL上添加参数的话,那么你可以使用
标签。比如,如下的
使用两个内嵌的
标签,来设置“/spittles”的max和count参数:
如果我们需要创建带有路径(path)参数的URL,我们该如何设置》
例如,假设我们需要为特定用户的基本信息页面创建一个URL。那没有问题,
标签可以承担此任务:
当href属性中的占位符匹配
中所指定的参数时,这个参数将会插入到占位符的位置中。如果
参数无法匹配href中的任何占位符,那么这个参数将会作为查询参数。
标签还可以解决URL的转义需求。例如,如果你希望将渲染得到的URL内容展现在Web页面上(而不是作为超链接),那么你应该要求
进行HTML转义,这需要将htmlEscape属性设置为true。例如,如下
将会渲染HTML转义后的URL:
所渲染的URL结果如下所示:
/spitter/spittles?max=60&count=20
另一方面,如果你希望在JavaScript代码中使用URL的话,那么应该将javaScriptEscape属性设置为true:
var spittlesUrl = "${spittlesJSUrl}"
这会渲染如下的结果到响应之中:
var spittlesUrl = "\/spitter\/spittles?max=60&count=20"
转义内容
标签是一个通用的转义标签。它会渲染标签体中内嵌的内容,并且在必要的时候进行转义。
例如,假设你希望在页面上展现一个HTML代码片段。为了正确显示,我们需要将“”字符替换为“”,否则的话,浏览器将会像解析页面上其他HTML那样解析这段HTML内容。
当然,没有人禁止我们手动将其转义为“”,但是这很繁琐,并且代码难以阅读。我们可以使用
,并让Spring完成这项任务:
Hello
Hello
它将会在响应体中渲染成如下的内容:
>h1/h1<
虽然转义后的格式看起来很难读,但浏览器可以将其转换为未转义的HTML。
通过设置javaScriptEscape属性,
标签还支持JavaScript转义:
Hello
Hello
只完成一件事,并且完成得非常好。与
不同,它只会渲染内容,并不能将内容设置为变量。
6.3 小结
6.3 小结
处理请求只是Spring MVC功能的一部分。如果控制器所产生的结果想要让人看到,那么它们产生的模型数据就要渲染到视图中,并展现到用户的Web浏览器中。Spring的视图渲染是很灵活的,并提供了多个内置的可选方案,包括传统的JavaServer Pages以及流行的Apache Tiles布局引擎。
在本章中,我们首先快速了解了一下Spring所提供的视图和视图解析可选方案。我们还深入学习了如何在Spring MVC中使用JSP和Apache Tiles。
我们还看到了如何使用Thymeleaf作为Spring MVC应用的视图层,它被视为JSP的替代方案。Thymeleaf是一项很有吸引力的技术,因为它能创建原始的模板,这些模板是纯HTML,能像静态HTML那样以原
始的方式编写和预览,并且能够在运行时渲染动态模型数据。除此之外,Thymeleaf是与Servlet没有耦合关系的,这样它就能够用在JSP所不能使用的领域中。
Spittr应用的视图定义完成之后,我们已经具有了一个虽然微小但是可部署且具有一定功能的Spring MVC Web应用。还有一些其他的特性需要更新进来,如数据持久化和安全性,我们会在合适的时候关注这些特性。但现在,这个应用开始变得有模有样了。
在深入学习应用的技术栈之前,在下一章我们将会继续讨论Spring MVC,学习这个框架中一些更为有用和高级的功能。
原文链接:
https://thinkwon.blog.csdn.net/article/details/103559672