2.2.3 Spring 通用的标签库
除了表单绑定标签库之外,Spring还提供了更为通用的JSP标签库。要使用它,必须在页面上对其进行声明:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
| JSP标签 | 描述 |
|---|---|
<s:bind> |
将绑定属性的状态导出到一个名为status的页面作用域属性中,与<s:path>组合使用获取绑定属性的值 |
<s:escapeBody> |
将标签体中的内容进行HTML和/或JS转义 |
<s:hasBindErrors> |
根据指定模型对象(在请求属性中)是否有绑定错误,有条件地渲染内容 |
<s:htmlEscape> |
为当前页面设置默认的HTML转义值 |
<s:message> |
根据给定的编码获取信息,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用var和scope属性实现) |
<s:nestedPath> |
设置嵌入式的path,用于<s:bind>之中 |
<s:theme> |
根据给定的编码获取主题信息,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用var和scope属性实现) |
<s:transform> |
使用命令对象的属性编辑器转换命令对象中不包含的属性 |
<s:url> |
创建相对于上下文的URL,支持URI模版变量以及HTML/XML/JS转义,可以渲染URL(默认行为),也可以将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用var和scope属性实现) |
<s:eval> |
计算符合Spring表达式语言(SpEL)语法的某个表达式的值,然后然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用var和scope属性实现) |
2.2.4 展现国际化信息
在进行国际化中,对于渲染文本来说,<s:message>是很好的方案:
<h1><s:message code="spittr.welcome" /></h1>
按照这里的方式,<s:message>将会根据key为spittr.welcome的信息来渲染文本。因此,如果希望此标签能完成任务,就需要配置一个这样的信息源。Spring有多个信息源的类,它们都实现了MessageSource接口。比较常用的是ResourceBundleMessageSource。它会从一个属性文件中加载信息,这个属性文件的名称是根据基础名称衍生而来的:
@Bean
public MessageSource messageSource(){
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("message");
return messageSource;
}
在上面代码中,核心在于设置basename属性,将其设置为message后,ResourceBundleMessageSource就会试图在根路径的属性文件中解析信息,这些属性文件的名称是根据这个基础名衍生得到的(message.properties)。
另外可选方案是使ReloadableResourceBundleMessageSource,使用和工作方式和ResourceBundleMessageSource非常类似,但是它能够重新加载信息属性,而不必重新编译或重启应用。
@Bean
public MessageSource messageSource(){
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("file:///etc/spittr/message");
messageSource.setCacheSeconds(10);
return messageSource;
}
说明:这里basename设置为在应用外部查找,还可以设置为在类路径下(以“classpath:”作为前缀)、文件系统中(以“file:”作为前缀)或Web应用的根路径下(没有前缀)查找属性。
2.2.5 创建URL
<s:url>是一个很小的标签,其主要任务就是创建URL,然后将其赋值给一个变量或者渲染到响应中。它是JSTL中<c:url>标签的替代者,但是有几项特殊技巧。
按照其最简单的形式,<s:url>会接受一个相对于Servlet上下文的URL,并在渲染的时候,预先添加上Servlet上下文路径。
<a href="<s:url href="/spitter/register" />">Register</a>
如果应用的Servlet上下文名为spittr,那么响应中将渲染为如下的HTML:
<a href="/spittr/spitter/register">Register</a>
还可以使用<s:url>创建URL,并将其赋值给一个变量供模版在稍后使用:
<s:url href="/spitter/register" var="registerUrl" />
<a href="${registerUrl}">Register</a>
默认情况,URL是在页面作用域内创建的。但是通过设置scope属性,我们可以让<s:url>在应用作用域内、会话作用域内或请求作用域内创建URL:
<s:url href="/spitter/register" var="registerUrl" scope="request" />
如果希望在URL上添加参数的话,那么你可以使用<s:param>标签。
<s:url href="/spittles" var="spittlesUrl">
<s:param name="max" value="60">
<s:param name="count" value="20">
</s:url>
上面讲到的功能,在JSTL <c:url>中也有,但是如果向要创建带有路径参数的URL,则需要使用<s:url>了。
<s:url href="/spittles/{username}" var="spittlesUrl">
<s:param name="username" value="jack">
</s:url>
当href属性中的占位符匹配<s:param>中所指定的参数时,这个参数将会插入到占位符的位置中。如果<s:param>参数无法匹配href中的任何占位符,那么这个参数(此处是username)将会作为查询参数。
<s:url>标签还可以解决URL的转移需求,如过希望将渲染得到的URL内容展现在Web页面上(而不是作为超链接),可以这样:
<s:url href="/spittles" htmlEscape="true">
<s:param name="max" value="60">
<s:param name="count" value="20">
</s:url>
所渲染的结果如下:
/spitter/spittles?max=60&count=20
当然如果想在JS中使用URL则需要设置javascriptEscape属性:
<s:url href="/spittles" javascriptEscape="true" var="spittlesJSUrl">
<s:param name="max" value="60">
<s:param name="count" value="20">
</s:url>
使用如下:
<script>
var spittlesUrl = "{spittlesJSUrl}"
</script>
渲染结果为:
<script>
var spittlesUrl = "\/spitter\/spittles?max=60&count=20"
</script>
2.2.6 转义内容
<s:escapeBody>标签是一个通用的转义标签。它会渲染标签中内嵌的内容,并且在必要的时候进行转义。如想在页面展现一个HTML代码片段:
<s:escapeBody htmlEscape="true">
<h1>Hello</h1>
</s:escapeBody>
它将会渲染成如下内容:
<h1>Hello</h1>
在浏览器中显示的时候就会自动转义为:
<h1>Hello</h1>
当然还可以设置javascriptEscape属性对JS进行转义,但是此标签不能将内容设置为变量。
三、使用 Apache Tiles 视图定义布局
如果我们想为应用中的所有页面定义一个通用的头部和底部。最原始的方式就是查找每个JSP,并为其添加头部和底部的HTML。但是这种方式的扩展性并不好。更好的方式是使用布局引擎,如Apache Tiles,定义适用于所有页面的通用页面布局。
3.1 配合Tiles视图解析器
为了在Spring中使用Tiles,需要配置几个bean。需要一个TilesConfigurer bean,它会负责定位和加载Tile定义并协调生成Tiles。除此之外,还需要TilesViewResolver bean将逻辑视图名称解析为Tile定义。
这两个组件又有两种形式:针对Apache Tiles 2(位于org.springframework.web.servlet.view.tiles2)和Apache Tiles 3(位于org.springframework.web.servlet.view.tiles3)分别都有这么两个组件。
首先,配置TilesConfigurer来解析Tile定义。
@Bean
public TilesConfigurer tilesConfigurer(){
TilesConfigurer tiles = new TilesConfigurer();
tiles.setDefinitions(new String[] {"/WEB-INF/layout/tiles.xml"});
tiles.setCheckRefresh(true);
return tiles;
}
说明:配置TilesConfigurer的时候,所要设置的最重要的属性就是definitions。接受一个String类型的数组,其中每个条目都指定一个Tile定义的XML文件。这里让它在"/WEB-INF/layout/"下查找tiles.xml文件。如果我们想让其加载"/WEB-INF/"目录下的所有名为tiles.xml的文件则可以这样配置路径:/WEB-INF/**/tiles.xml。
接下来,让我们配置TilesViewResolver,可以看到,这是一个很基本的bean定义,没有什么要设置的属性:
@Bean
public ViewResolver viewResolver(){
return new TilesViewResolver();
}
当然也可以使用XML的方式进行配置:
<bean id="tilesCOnfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/layout/tiles.xml</value>
<value>/WEB-INF/**/tiles.xml</value>
</list>
</property>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.tiles3.TilesViewResolver" />
说明:TilesConfigurer会加载Tile定义并与Apache Tiles协作,而TilesViewResolver会将逻辑视图名称解析为引用Tile定义的视图。
3.1.1 定义Tiles
下面看如何定义Tiles文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<!--定义base Tile-->
<definition name="base" template="/WEB-INF/layout/page.jsp">
<!--设置属性-->
<put-attribute name="header" value="/WEB-INF/layout/header.jsp" />
<put-attribute name="footer" value="/WEB-INF/layout/footer.jsp" />
</definition>
<definition name="home" extends="base"><!--扩展base Tile-->
<put-attribute name="body" value="/WEB-INF/views/home.jsp" />
</definition>
<definition name="registerForm" extends="base"><!--扩展base Tile-->
<put-attribute name="body" value="/WEB-INF/views/registerForm.jsp" />
</definition>
<definition name="profile" extends="base"><!--扩展base Tile-->
<put-attribute name="body" value="/WEB-INF/views/profile.jsp" />
</definition>
<definition name="spittles" extends="base"><!--扩展base Tile-->
<put-attribute name="body" value="/WEB-INF/views/spittles.jsp" />
</definition>
<definition name="spittle" extends="base"><!--扩展base Tile-->
<put-attribute name="body" value="/WEB-INF/views/spittle.jsp" />
</definition>
</tiles-definitions>
说明:每个<definition>元素都定义了一个Tile,最终引用的是一个JSP模版。可以看到base Tile中设置了基本的模版,及顶部和底部。而后面的Tile都是用来扩展base Tile的,后面的Tile都将作为body插入到整个页面,和顶部、底部一起组成一个完整的页面。我们看base Tile所引用的page.jsp模版:
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="t" %>
<%@ page session="false" %>
<html>
<head>
<title>Spittr</title>
<link rel="stylesheet" type="text/css" href="<s:url value="/resources/style.css" />" >
</head>
<body>
<div id="header">
<t:insertAttribute name="header" />
</div>
<div id="content">
<t:insertAttribute name="body" />
</div>
<div id="footer">
<t:insertAttribute name="footer" />
</div>
</body>
</html>
说明:从上面可以看到三个页面是如何组成一个完整的页面的,其实扩展的Tile会继承base Tile的相关属性,如home Tile实际上包含了如下定义:
<definition name="home" template="/WEB-INF/layout/page.jsp">
<put-attribute name="header" value="/WEB-INF/layout/header.jsp" />
<put-attribute name="footer" value="/WEB-INF/layout/footer.jsp" />
<put-attribute name="body" value="/WEB-INF/views/home.jsp" />
</definition>
为了完整的了解home Tile,如下展现了home.jsp:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<h1>Welcome to Spittr</h1>
<a href="<c:url value="spittles" />">Spittles</a>
<a href="<c:url value="spittler/register" />">Register</a>
四、使用Thymeleaf
JSP规范与Servlet规范紧密耦合,这意味着它只能用在基于Servlet的Web应用之中。JSP模版不能作为通用的模版(如格式化Email),也不能用于非Servlet的Web应用。
4.1 配置Thymeleaf视图解析器
为了要在Spring中使用Thymeleaf,需要配置三个启用Thymeleaf与Spring集成的bean:
-
ThymeleafViewResolver:将逻辑试图名称为解析为Thymeleaf模版视图; -
SpringTemplateEngine:处理模版并渲染结果; -
TemplateResolver:加载Thymeleaf模版。
如下为声明这些bean的Java配置(在WebConfig.java中):
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
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/views/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML5");
return templateResolver;
}
当然也可以使用XML方式进行配置:
<bean id="viewResolver" class="org.thymeleaf.spring3.view.ThymeleafViewResolver"
p:templateEngine-ref="templateEngine" />
<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine"
p:templateEngine-ref="templateResolver" />
<bean id="templateResolver" class="org.thymeleaf.templateresolver.ServletContextTemplateResolver"
p:prefix="/WEB-INF/templates"
p:suffix=".html"
p:templateMode="HTML5"/>
说明:ThymeleafViewResolver是Spring MVC中ViewResolver的一个实现,会将一个逻辑视图名解析为一个Thymeleaf模版。在ThymeleafViewResolver bean中注入了一个对SpringTemplateEngine bean的引用。SpringTemplateEngine会在Spring中启用Thymeleaf引擎,用来解析模版,并基于这些模版渲染结果。可以看到,我们为其注入了一个TemplateResolver bean的引用。TemplateResolver会最终定位和查找模版。
4.2 定义Thymeleaf模版
Thymeleaf在很大程度上就是HTML文件,与JSP不同,它没有什么特殊的标签或标签库。它通过自定义的命名空间,为标准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>
<div id="header" th:include="page :: header"></div>
<div id="content">
<h1>Welcome to Spitter</h1>
<!--到页面的th:href链接-->
<a th:href="@{/spittles}">Spittles</a> |
<a th:href="@{/spitter/register}">Register</a>
</body>
</html>
说明:th:href属性的特殊之处在于它的值中可以包含Thymeleaf表达式,用来计算动态的值。Thymeleaf的价值在于它与纯HTML模版非常接近。唯一的区别就是使用了th:href属性。这意味着Thymeleaf模版与JSP不同,它能够按照原始的方式进行编辑甚至渲染,而不必经过任何类型的处理器。
4.2.1 借助Thymeleaf实现表单绑定
下面直接通过表单页面registerForm.html进行说明:
<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的方言。th:class属性会渲染为一个class属性,它的值是根据给定的表达式计算得到的。如果有有错误,class在渲染时的值为error,如果这个域没有错误,将不会渲染class属性。th:field属性用来引用后端对象的相关域(如spitter的firstName属性),其中星号表示为所有表单对象(此处为spitter)定义后端对象。使用th:if属性来检查是否有校验错误,如果有,会渲染<div>,否则不渲染。<li>标签上的th:each属性将会通知Thymeleaf为每项错误都渲染一个<li>,在每次迭代中会将当前错误设置到一个名为err的变量中。
“${}”和“*{}”有什么区别:前者是变量表达式。一般来讲,会是OGNL表达式,在使用Spring时是SpEL表达式。在${spitter}例子中,会解析key为spitter的model属性。而后者是选择表达式。变量表达式是基于整个SpEL上下文计算的,而选择表达式是基于一个选中对象计算的。本例中选择的是Spitter对象。