16、渲染Web视图(2)(Spring笔记)

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> 根据给定的编码获取信息,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用varscope属性实现)
<s:nestedPath> 设置嵌入式的path,用于<s:bind>之中
<s:theme> 根据给定的编码获取主题信息,然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用varscope属性实现)
<s:transform> 使用命令对象的属性编辑器转换命令对象中不包含的属性
<s:url> 创建相对于上下文的URL,支持URI模版变量以及HTML/XML/JS转义,可以渲染URL(默认行为),也可以将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用varscope属性实现)
<s:eval> 计算符合Spring表达式语言(SpEL)语法的某个表达式的值,然后然后要么进行渲染(默认行为),要么将其设置为页面作用域、请求作用域、会话作用域或应用作用域的变量(通过使用varscope属性实现)

2.2.4 展现国际化信息

在进行国际化中,对于渲染文本来说,<s:message>是很好的方案:

<h1><s:message code="spittr.welcome" /></h1>

按照这里的方式,<s:message>将会根据keyspittr.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规范紧密耦合,这意味着它只能用在基于ServletWeb应用之中。JSP模版不能作为通用的模版(如格式化Email),也不能用于非ServletWeb应用。

4.1 配置Thymeleaf视图解析器

为了要在Spring中使用Thymeleaf,需要配置三个启用ThymeleafSpring集成的bean

  • ThymeleafViewResolver:将逻辑试图名称为解析为Thymeleaf模版视图;
  • SpringTemplateEngine:处理模版并渲染结果;
  • TemplateResolver:加载Thymeleaf模版。

如下为声明这些beanJava配置(在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"/>

说明:ThymeleafViewResolverSpring MVCViewResolver的一个实现,会将一个逻辑视图名解析为一个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属性用来引用后端对象的相关域(如spitterfirstName属性),其中星号表示为所有表单对象(此处为spitter)定义后端对象。使用th:if属性来检查是否有校验错误,如果有,会渲染<div>,否则不渲染。<li>标签上的th:each属性将会通知Thymeleaf为每项错误都渲染一个<li>,在每次迭代中会将当前错误设置到一个名为err的变量中。

“${}”“*{}”有什么区别:前者是变量表达式。一般来讲,会是OGNL表达式,在使用Spring时是SpEL表达式。在${spitter}例子中,会解析keyspittermodel属性。而后者是选择表达式。变量表达式是基于整个SpEL上下文计算的,而选择表达式是基于一个选中对象计算的。本例中选择的是Spitter对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容