《Spring 实战》笔记6:渲染 Web 视图
理解视图解析
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<String, ?> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception;
}
View
接口的任务就是接受模型以及 Servlet
的 request
和 response
对象,并将输出结果渲染到 response
中。
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.0UrlBasedViewResolver
直接根据视图的名称解析视图,视图的名称会匹配一个物理视图的定义VelocityLayoutViewResolver
将视图解析为Velocity
布局,从不同的Velocity
模板中组合页面VelocityViewResolver
将视图解析为Velocity
模板XmlViewResolver
将视图解析为特定 XML 文件中的 bean 定义。类似于BeanNameViewResolver
XsltViewResolver
将视图解析为 XSLT 转换后的结果
创建 JSP 视图
Spring 提供了两种支持 JSP 视图的方式:
- InternalResourceViewResolver 会将视图名解析为 JSP 文件。另外,如果在你的 JSP 页面中使用了 JSP 标准标签库(JavaServer Pages Standard Tag Library,JSTL)的话,InternalResourceViewResolver 能够将视图名解析为 JstlView 形式的 JSP 文件,从而将 JSTL 本地化和资源 bundle 变量暴露给 JSTL 的格式化(formatting)和信息(message)标签。
- Spring 提供了两个 JSP 标签库,一个用于表单到模型的绑定,另一个提供了通用的工具类特性。
配置适用于 JSP 的视图解析器
InternalResourceViewResolver 遵循一种约定,会在视图名上添加前缀和后缀,进而确定一个 Web 应用中视图资源的物理路径。
@Bean
public ViewResolver viewResolver() {
InternalResourceVIewResolver resolver = new InternalResourceVIewResolver();
resolver.setPrefix("WEB-INF/view");
resolver.setSuffix(".jsp")
return resolver;
}
作为替代方法,如果你更喜欢基于 XML 的 Spring 配置,那么可以按照如下方式配置 InternalResourceVIewResolver
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceVIewResolver"
p:prefix="/WEB-INF/view"
p:suffix=".jsp"
JSTL 的格式化标签需要一个 Locale
对象,以便于恰当地格式化地域相关的值,如日期和货币。信息标签可以借助 Spring 的信息资源和 Locale,从而选择适当的信息渲染到 HTML 之中。通过解析 JstlView
,JSTL 能够获得 Locale 对象以及 Spring 中配置的信息资源。
如果想让 InternalResourceViewResolver 将视图解析为 JstlView,而不是 InternalResourceView 的话,那么我们只需设置它的 viewClass 属性即可:
@Bean
public ViewResolver viewResolver() {
InternalResourceVIewResolver resolver = new InternalResourceVIewResolver();
resolver.setPrefix("WEB-INF/view");
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/view"
p:suffix=".jsp"
p:viewClass="org.springframework.web.servlet.view.JstlView.class"
使用 Spring 的 JSP 库
用处不多。
将表单绑定到模型上
需要在JSP页面中对其进行声明
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
<sf:checkbox>
<sf:checkboxes>
<sf:errors>
<sf:form>
<sf:hidden>
<sf:input>
<sf:lable>
<sf:option>
<sf:password>
<sf:radiobutton>
<sf:radiobuttons>
<sf:select>
<sf:textarea>
<sf:radilbutton>
<sf:form method="POST" comandName="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:password path="password" /><br/>
<sf:input type="submit" value="Register" />
</sf:form>
<sf:form>
会渲染一个 HTMl <form>
标签,但它也会通过 commandName
属性构建针对某个模型对象的上下文信息,在其他的表单绑定标签中,会引用这个模型对象的属性
我们将 commandName
属性设置为spitter。因此,在模型中必须要有一个 key 为 spitter 的对象,否则的话,表单不能正常渲染(会出现 JSP 错误)
从 Spring3.1 开始 <sf:input>
标签能够允许我们指定 type 属性,这样的话,除了其他可选的类型外,还能指定 HTML5 特定类型的文本域,如 date、range、email。我们可以按照如下方式指定 email 域:
Email: <sf:input path="email" type="email"/> <br/>
使用 <sf:errors>
<sf:form method="POST" commandName="spitter" >
<sf:input path="firstName" /><br/>
<sf:errors path="firstName" cssErrorClass="error" />
......
</sf:form>
@NotNull
@Size(min=5, max=16, message="{username.size}")
private String username;
.......
@NotNull
@Size(min=2, max=30, message="{lastName.size}")
private String lastName;
我们将其 @Size
注解的 message 设置为一个字符串,用大括号括起来的。没有没有大括号的话,message 中的值将会作为展现给用户的错误信息,使用了就用文件中某个一个属性,该属性包含了实际的信息。
创建一个 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.
Spring 通用的标签库
需要在页面首页对其进行声明
<%@ taglib uri="http://www.springframework.org.tags" prefix='s'%>
<s:escapeBody>
将标签体中的内容进行 HTML 和 / 或 JavaScript 转义,包裹要转义内容,标签属性加上htmlEscape="true"
javaScriptEscape="true
<s:hasBindErrors>
根据指定模型对象(在请求属性中)是否有绑定错误,有条件地渲染内容<s:htmlEscape>
为当前页面设置默认的 HTML 转义值<s:message>
根据给定的编码获取信息,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用 var 和 scope 属性实现)<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 属性实现)
使用 Apache Tiles 视图定义布局
使用 Thymeleaf
Thymeleaf
模板是原生的,不依赖于标签库。它能在接受原始 HTML 的地方进行编辑和渲染。因为它没有与 Servlet 规范耦合,因此 Thymeleaf
模板能够进入 JSP 所无法涉足的领域。
配置 Thymeleaf 视图解析器
ThymeleafViewResolver
:将逻辑视图名称解析为 Thymeleaf 模板视图;SpringTemplateEngine
:处理模板并渲染结果;TemplateResolver
:加载 Thymeleaf 模板。
// 声明这些 bean 的 Java 配置
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
// Thymeleaf 视图解析器
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
@Bean
public TemplateEngine templateEngine(TemplateResolver templateResolver) {
// 模板引擎
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
@Bean
public TemplateResolver templateResolver() { // 模板解析器
TemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/templates");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("THML5");
return templateResolver;
}
<!-- 使用 XML 的方式,配置 Spring 对 Thymeleaf 的支持 -->
<bean id="viewResolver" class="org.thymeleaf.spring4.view.ThymeleafViewResolver"
p:templateEngine-ref="templateEngine" />
<bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine"
P:templateResolver-ref="templateResolver" />
<bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver"
p:prefix="WEB-INF/templates/"
p:suffix=".html"
p:templateMode="HTML5" /> <!-- 预期要解析的模板会渲染成 HTML5 输出 -->
定义 Thymeleaf 模板
Thymeleaf 在很大程度上就是 HTML 文件,与 JSP 不同,它没有什么特殊的标签或标签库。Thymeleaf 之所以能够发挥作用,是因为它通过自定义的命名空间,为标准的 HTML 标签集合添加 Thymeleaf 属性。如下的程序清单展现了 home.html,也就是使用 Thymeleaf 命名空间的首页模板。
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"> <!--声明 Thymeleaf 命名空间-->
<head>
<title>Spitter</title>
<link rel="stylesheet"
type="text/css"
th:href="@{/resources/style.css}"></link> <!--到样式表的 th:href 链接-->
</head>
<body>
<h1>Welcome to Spitter</h1>
<a th:href="@{/spittles}">Spittles</a> | <!--到页面的 th:herf 链接-->
<a th:href="@{/spitter/register}">Register</a>
</body>
</html>
使用了 th:href
属性,都用到了 @{}
表达式,用来计算相对于 URL 的路径
借助 Thymeleaf 实现表单绑定
<label th:class="${#fields.hasErrors('firstName')}? 'error'">First Name</label>:
<input type="text" th:field="*{firstName}"
th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>
th:class
属性会渲染为一个 class 属性,它的值是根据给定的表达式计算得到的。在上面的这两个 th:class
属性中,它会直接检查 firstName 域有没有校验错误。如果有的话,class 属性在渲染时的值为 error。如果这个域没有错误的话,将不会渲染 class 属性。
<input>
标签使用了 th:field
属性,用来引用后端对象的 firstName 域。这可能与你的预期有点差别。在 Thymeleaf 模板中,我们在很多情况下所使用的属性都对应于标准的 HTML 属性,因此貌似使用 th:value
属性来设置 <input>
标签的 value 属性才是合理的。
其实不然,因为我们是在将这个输入域绑定到后端对象的 firstName 属性上,因此使用 th:field
属性引用 firstName 域。通过使用 th:field
,我们将 value 属性设置为 firstName 的值,同时还会将 name 属性设置为 firstName。
完整的注册表单模板
<form method="POST" th:object="${spitter}">
<div class="errors" th:if="${#fields.hasErrors('*')}">
<ul>
<li th:each="err : ${#fields.errors('*')}"
th:text="${err}">Input is incorrect</li>
</ul>
</div>
<label th:class="${#fields.hasErrors('firstName')}? 'error'">First Name</label>:
<input type="text" th:field="*{firstName}"
th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('lastName')}? 'error'">Last Name</label>:
<input type="text" th:field="*{lastName}"
th:class="${#fields.hasErrors('lastName')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('email')}? 'error'">Email</label>:
<input type="text" th:field="*{email}"
th:class="${#fields.hasErrors('email')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('username')}? 'error'">Username</label>:
<input type="text" th:field="*{username}"
th:class="${#fields.hasErrors('username')}? 'error'" /><br/>
<label th:class="${#fields.hasErrors('password')}? 'error'">Password</label>:
<input type="password" th:field="*{password}"
th:class="${#fields.hasErrors('password')}? 'error'" /><br/>
<input type="submit" value="Register" />
</form>
程序清单使用了相同的 Thymeleaf 属性和 *{}
表达式,为所有的表单域绑定后端对象。
<div>
元素使用 th:if
属性来检查是否有校验错误。如果有的话,会渲染 <div>
,否则的话,它将不会渲染。
在 <div>
中,会使用一个无顺序的列表来展现每项错误。<li>
标签上的 th:each
属性将会通知 Thymeleaf
为每项错误都渲染一个 <li>
,在每次迭代中会将当前错误设置到一个名为 err
的变量中。
<li>
标签还有一个 th:text
属性。这个命令会通知 Thymeleaf
计算某一个表达式(在本例中,也就是 err 变量)并将它的值渲染为 <li>
标签的内容体。实际上的效果就是每项错误对应一个 <li>
元素,并展现错误的文本。
${}
和 *{}
括起来的表达式到底有什么区别。 ${}
表达式(如 ${spitter}
)是变量表达式(variable expression)。一般来讲,它们会是对象图导航语言(Object-Graph Navigation Language,OGNL)表达式
。但在使用 Spring 的时候,它们是 SpEL 表达式。在 ${spitter}
这个例子中,它会解析为 key 为 spitter 的 model 属性。
而对于 *{}
表达式,它们是选择表达式(selection expression)。变量表达式是基于整个 SpEL 上下文计算的,而选择表达式是基于某一个选中对象计算的。在本例的表单中,选中对象就是 <form>
标签中 th:object
属性所设置的对象:模型中的 Spitter 对象。因此,*{firstName}
表达式就会计算为 Spitter 对象的 firstName 属性。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《Spring 实战》笔记6:渲染 Web 视图
文章字数:3.5k
本文作者:Bin
发布时间:2019-08-06, 20:31:36
最后更新:2019-12-11, 22:06:46
原始链接:http://coolview.github.io/2019/08/06/Spring/%E3%80%8ASpring%20%E5%AE%9E%E6%88%98%E3%80%8B%E7%AC%94%E8%AE%B06%EF%BC%9A%E6%B8%B2%E6%9F%93%20Web%20%E8%A7%86%E5%9B%BE/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。