16、渲染Web視圖(2)(Spring筆記)

2.2.3 Spring 通用的標簽庫

除了表單綁定標簽庫之外,Spring還提供了更為通用的JSP標簽庫。要使用它,必須在頁面上對其進行聲明:

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
JSP標簽 描述
<s:bind> 將綁定屬性的狀態(tài)導出到一個名為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> 創(chuàng)建相對于上下文的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 創(chuàng)建URL

<s:url>是一個很小的標簽,其主要任務就是創(chuàng)建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>創(chuàng)建URL,并將其賦值給一個變量供模版在稍后使用:

<s:url href="/spitter/register" var="registerUrl" />
<a href="${registerUrl}">Register</a>

默認情況,URL是在頁面作用域內創(chuàng)建的。但是通過設置scope屬性,我們可以讓<s:url>在應用作用域內、會話作用域內或請求作用域內創(chuàng)建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>中也有,但是如果向要創(chuàng)建帶有路徑參數的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模版??梢钥吹?code>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規(guī)范與Servlet規(guī)范緊密耦合,這意味著它只能用在基于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引擎,用來解析模版,并基于這些模版渲染結果??梢钥吹?,我們?yōu)槠渥⑷肓艘粋€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表達式,用來計算動態(tài)的值。Thymeleaf的價值在于它與純HTML模版非常接近。唯一的區(qū)別就是使用了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的變量中。

“${}”“*{}”有什么區(qū)別:前者是變量表達式。一般來講,會是OGNL表達式,在使用Spring時是SpEL表達式。在${spitter}例子中,會解析keyspittermodel屬性。而后者是選擇表達式。變量表達式是基于整個SpEL上下文計算的,而選擇表達式是基于一個選中對象計算的。本例中選擇的是Spitter對象。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容