天天看點

SpringInAction筆記(六)—— 渲染Web視圖(下)

3、使用Apache Tiles視圖定義布局

       使用布局引擎,如Apache Tiles,定義适用于所有頁面的通用頁面布局。Spring MVC以視圖解析器的形式為Apache Tiles提供了支援,這個視圖解析器能夠将邏輯視圖名解析為Tile定義。

       需導入Tiles相關的jar包:

  tiles-api-3.0.8.jar

  tiles-core-3.0.8.jar

  tiles-jsp-3.0.8.jar

  tiles-servlet-3.0.8.jar

  tiles-template-3.0.8.jar

       slf4j-api-1.7.25  (java.lang.ClassNotFoundException: org.slf4j.LoggerFactory)

       commons-digester-2.1 (java.lang.ClassNotFoundException: org.apache.commons.digester.Rule)

       commons-beanutils-1.9.3 (ClassNotFoundException: org.apache.commons.beanutils.MethodUtils)

       tiles-request-api-1.0.7.(java.lang.NoClassDefFoundError: org/apache/tiles/request/render/Renderer)

       tiles-request-servlet-1.0.7.(ClassNotFoundException: org.apache.tiles.request.servlet.ServletApplicationContext)

       tiles-request-jsp-1.0.7 (ClassNotFoundException: org.apache.tiles.request.jsp.autotag.JspAutotagRuntime)

       tiles-autotag-core-runtime-1.2 (ClassNotFoundException: org.apache.tiles.autotag.core.runtime.AutotagRuntime)

3.1 配置Tiles視圖解析器

       為了在Spring中使用Tiles,需要配置幾個bean。我們需要一個TilesConfigurer bean,它會負責定位和加載Tile定義并協調生成Tiles。除此之外,還需要TilesViewResolver bean将邏輯視圖名稱解析為Tile定義。

       這兩個元件又有兩種形式:針對Apache Tiles 2和Apache Tiles 3分别都有這麼兩個元件。這兩組Tiles元件之間最為明顯的差別在于包名。針對Apache Tiles 2的TilesConfigurer/TilesViewResolver位于org.springframework.web.servlet.view.tiles2包中,而針對Tiles 3的元件位于org.springframework.web.servlet.view.tiles3包中。對于該例子來講,我們使用的是Tiles 3。

WebConfig.java:

package spittr.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;

@Configuration
@EnableWebMvc  //啟用Spring Mvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {

	/**
	 * 配置Tiles視圖解析器
	 * @return
	 */
	@Bean
	public ViewResolver viewResolver() {	
		return new TilesViewResolver();
	}
	  
	// Tiles 
	@Bean	  
	public TilesConfigurer tilesConfigurer() {
	    TilesConfigurer tiles = new TilesConfigurer();
	    tiles.setDefinitions(new String[] {
	        "/WEB-INF/layout/tiles.xml",
	    });
	    //啟用重新整理功能
	    tiles.setCheckRefresh(true);
	    return tiles;	  
	}

	/**
	 * 配置靜态資源的處理
	 * 此配置要求DispatcherServlet将對靜态資源的請求轉發到Servlet容器中預設的Servlet上,
	 * 而非使用DispatcherServlet本身來處理此類請求
	 */
	@Override
	public void configureDefaultServletHandling(
			DefaultServletHandlerConfigurer configurer) {
		// TODO Auto-generated method stub
		//super.configureDefaultServletHandling(configurer);
		configurer.enable();
	}	
	  
	@Bean	  
	public MessageSource messageSource() {
	    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
	    messageSource.setBasename("/resources/messages");
	    messageSource.setCacheSeconds(10);
	    return messageSource;	  
	}
	
}
           

當配置TilesConfigurer的時候,所要設定的最重要的屬性就是definitions。這個屬性接受一個String類型的數組,其中每個條目都指定一個Tile定義的XML檔案。對于Spittr應用來講,我們讓它在“/WEB-INF/layout/”目錄下查找tiles.xml。

       還可以指定多個Tile定義檔案,甚至能夠在路徑位置上使用通配符,當然在上例中我們沒有使用該功能。例如,我們要求TilesConfigurer加載“/WEB-INF/”目錄下的所有名字為tiles.xml的檔案,那麼可以按照如下的方式設定definitions屬性:

tiles.setDefinitions(new String[] {
	        "/WEB-INF/**/tiles.xml",
	    });
           

       接下來配置TilesViewResolver,可以看到,這是一個很基本的bean定義,沒有什麼要設定的屬性:

@Bean
	public ViewResolver viewResolver() {	
		return new TilesViewResolver();
	}
           

定義Tiles

        Apache Tiles提供了一個文檔類型定義(document type definition,DTD),用來在XML檔案中指定Tile的定義。每個定義中需要包含一個<definition>元素,這個元素會有一個或多個<putattribute>元素。例如,如下的XML文檔為Spittr應用定義了幾個Tile。

        程式清單6.2 WEB-INF/layout/tiles.xml:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!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>

  <!-- 擴充base -->
  <definition name="home" extends="base">
    <put-attribute name="body" value="/WEB-INF/views/home.jsp" />
  </definition>

  <definition name="registerForm" extends="base">
    <put-attribute name="body" value="/WEB-INF/views/registerForm.jsp" />
  </definition>

  <definition name="profile" extends="base">
    <put-attribute name="body" value="/WEB-INF/views/profile.jsp" />
  </definition>

  <definition name="spittles" extends="base">
    <put-attribute name="body" value="/WEB-INF/views/spittles.jsp" />
  </definition>

  <definition name="spittle" extends="base">
    <put-attribute name="body" value="/WEB-INF/views/spittle.jsp" />
  </definition>

</tiles-definitions>
           

       每個<definition>元素都定義了一個Tile,它最終引用的是一個JSP模闆。在名為base的Tile中,模闆引用的是“/WEBINF/

layout/page.jsp”。某個Tile可能還會引用其他的JSP模闆,使這些JSP模闆嵌入到主模闆中。對于base Tile來講,它引用的是一個頭部JSP模闆和一個底部JSP模闆。

       程式清單6.3 主布局模闆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=" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" /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标簽庫中的<t:insert Attribute> JSP标簽來插入其他的模闆,在這裡,用它來插入名為header、body和footer的模闆.

        在base Tile定義中,header和footer屬性分别被設定為引用“/WEBINF/layout/ header. jsp”和“/WEB-INF/layout/footer.jsp”。但是body屬性呢?它是在哪裡設定的呢?

       base Tile不會期望單獨使用,它會作為基礎定義,供其他的Tile定義擴充。在程式清單6.2的其餘内容中,可以看到其他的Tile定義都是擴充自base Tile。它意味着它們會繼承其header和footer屬性的設定(當然,Tile定義中也可以覆寫掉這些性),但是每一個都設定了body屬性,用來指定每個Tile特有的JSP模闆。

       關注home Tile,它擴充了base。因為它擴充了base,是以它會繼承base中的模闆和所有的屬性。盡管home Tile定義相對來說很簡單,但是它實際上包含了如下的定義:

<definition name="home" extends="base">
    <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>
           

屬性所引用的每個模闆是很簡單的,如下是header.jsp模闆:

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<a href="<s:url value=" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" /" />"><img 
    src="<s:url value="/resources" />/images/spitter_logo_50.png" 
    /></a>
           

footer.jsp模闆更為簡單:

Copyright &copy; Craig Walls
           

每個擴充自base的Tile都定義了自己的主體區模闆,是以每個都會與其他的有所差別。

home.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<h1>Welcome to Spitter</h1>

<a href="<c:url value=" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" /spittles" />">Spittles</a> | 
<a href="<c:url value=" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" /spitter/register" />">Register</a>
           

這裡的關鍵點在于通用的元素放到了page.jsp、header.jsp以及footer.jsp中,其他的Tile模闆中不再包含這部分内容。這使得它們能夠跨頁面重用,這些元素的維護也得以簡化。

運作結果:

SpringInAction筆記(六)—— 渲染Web視圖(下)
SpringInAction筆記(六)—— 渲染Web視圖(下)
SpringInAction筆記(六)—— 渲染Web視圖(下)

4、使用Thymeleaf

        多年來,在Java應用中,有多個項目試圖挑戰JSP在視圖領域的統治 性地位。最新的挑戰者是Thymeleaf,它展現了一些切實的承諾,是 一項很令人興奮的可選方案。Thymeleaf模闆是原生的,不依賴于标簽庫。它能在接受原始HTML的地方進行編輯和渲染。因為它沒有與 Servlet規範耦合,是以Thymeleaf模闆能夠進入JSP所無法涉足的領域。

4.1 配置Thymeleaf視圖解析器

        為了要在Spring中使用Thymeleaf,我們需要配置三個啟用Thymeleaf與 Spring內建的bean:

        (1)ThymeleafViewResolver:将邏輯視圖名稱解析為Thymeleaf 模闆視圖;

        (2)SpringTemplateEngine:處理模闆并渲染結果;

        (3)TemplateResolver:加載Thymeleaf模闆。

需要引入以下jar包:

    thymeleaf-3.0.9

    thymeleaf-spring4-3.0.9

    attoparser-2.0.4

    unbescape-1.1.5

清單 WebConfig.java:

package spittr.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;

@Configuration
@EnableWebMvc  //啟用Spring Mvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {

	/**
	 * 配置Thymeleaf視圖解析器
	 * @return
	 */
	@Bean
	public ThymeleafViewResolver viewResolver(SpringTemplateEngine templateEngine) {	
	    ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
	    viewResolver.setTemplateEngine(templateEngine);
	    return viewResolver;
	}
	
	/**
	 * 模闆引擎,處理模闆并渲染結果
	 * @param templateResolver
	 * @return
	 */
	@Bean	  
	public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
	    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
	    templateEngine.setTemplateResolver(templateResolver);
	    return templateEngine;  
	}
	
	/**
	 * 模闆解析器
	 * @return
	 */
	@Bean  
	public ITemplateResolver templateResolver() {
		// SpringResourceTemplateResolver自動與Spring自己內建, 資源解決基礎設施, 強烈推薦。
		SpringResourceTemplateResolver  templateResolver = new SpringResourceTemplateResolver ();
	    templateResolver.setPrefix("/WEB-INF/views/");
	    templateResolver.setSuffix(".html");
	    //要解析的模闆會渲染成HTML5輸出
	    templateResolver.setTemplateMode("HTML5");
	    // 預設情況下, 模闆緩存為true。如果您想要設定為false。模闆在修改時自動更新。
	    templateResolver.setCacheable(true);
	    return templateResolver;	  
	}

	/**
	 * 配置靜态資源的處理
	 * 此配置要求DispatcherServlet将對靜态資源的請求轉發到Servlet容器中預設的Servlet上,
	 * 而非使用DispatcherServlet本身來處理此類請求
	 */
	@Override
	public void configureDefaultServletHandling(
			DefaultServletHandlerConfigurer configurer) {
		// TODO Auto-generated method stub
		//super.configureDefaultServletHandling(configurer);
		configurer.enable();
	}	
	  
	@Bean	  
	public MessageSource messageSource() {
	    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
	    messageSource.setBasename("/resources/messages");
	    messageSource.setCacheSeconds(10);
	    return messageSource;	  
	}
	
}
           

ThymeleafViewResolver是Spring MVC中ViewResolver的一個實作類。像其他的視圖解析器一樣,它會接受一個邏輯視圖名稱,并 将其解析為視圖。不過在該場景下,視圖會是一個Thymeleaf模闆。       

   需要注意的是ThymeleafViewResolver bean中注入了一個對 SpringTemplate Engine bean的引用。SpringTemplateEngine會在Spring中啟用Thymeleaf引擎,用來解析模闆,并基于這些模闆渲染結果。可以看到,我們為其注入了一個TemplateResolver bean的引用。      

      TemplateResolver會最終定位和查找模闆。與之前配 置InternalResource-ViewResolver類似,它使用了prefix和suffix屬性。字首和字尾将會與邏輯視圖名組合使用,進而定位Thymeleaf引擎。它的templateMode屬性被設定成了HTML 5,這表明我們預期要解析的模闆會渲染成HTML 5輸出。

4.2 定義Thymeleaf模闆        

Thymeleaf在很大程度上就是HTML檔案,與JSP不同,它沒有什麼特 殊的标簽或标簽庫。Thymeleaf之是以能夠發揮作用,是因為它通過 自定義的命名空間,為标準的HTML标簽集合添加Thymeleaf屬性。如下的程式清單展現了home.html,也就是使用Thymeleaf命名空間的首頁模闆。

清單 home.html:      

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" 
          type="text/css" 
          th:href="{/resources/style.css}" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" ></link>
  </head>
  <body>
    <div id="header" th:include="page :: header"></div>
  
    <div id="content">
      <h1>Welcome to Spitter</h1>
  
      <a th:href="@{/spittles}" target="_blank" rel="external nofollow" >Spittles</a> | 
      <a th:href="@{/spitter/register}" target="_blank" rel="external nofollow" >Register</a>
      
      <br/>
      
      View: <span th:text="${view}">unknown</span>
    </div>
    <div id="footer" th:include="page :: copy"></div>
  </body>
</html>
           

首頁模闆相對來講很簡單,隻使用了th:href屬性。這個屬性與對 應的原生HTML屬性很類似,也就是href屬性,并且可以按照相同的方式來使用。th:href屬性的特殊之處在于它的值中可以包含Thymeleaf表達式,用來計算動态的值。它會渲染成一個标準的href 屬性,其中會包含在渲染時動态建立得到的值。

       “@{}”表達式用來計算相對于 URL的路徑(就像在JSP頁面中,我們可能會使用的JSTL <c:url>标 簽或Spring<s:url>标簽類似)。

借助Thymeleaf實作表單綁定

       表單綁定是Spring MVC的一項重要特性。它能夠将表單送出的資料填充到指令對象中,并将其傳遞給控制器,而在展現表單的時候,表單 中也會填充指令對象中的值。如果沒有表單綁定功能的話,我們需要確定HTML表單域要映射後端指令對象中的屬性,并且在校驗失敗後 展現表單的時候,還要負責確定輸入域中值要設定為指令對象的屬 性。

       作為闡述的樣例,請參考如下的Thymeleaf模闆片段,它會渲染First Name輸入域:

<label th:class="${#fields.hasErrors('firstName')}? 'error'">First Name</label>: 
          <input type="text" th:field="*{firstName}"  
                 th:class="${#fields.hasErrors('firstName')}? 'error'" /><br/>
           

       在這裡,我們不再使用Spring JSP标簽中的cssClassName屬性,而是在标準的HTML标簽上使用th:class屬性。th:class屬性會渲染為一個class屬性,它的值是根據給定的表達式計算得到的。在上面的這兩個th:class屬性中,它會直接檢查firstName域有沒有校驗錯誤。如果有的話,class屬性在渲染時的值為error。如果這 個域沒有錯誤的話,将不會渲染class屬性。

       <input>标簽使用了th:field屬性,用來引用後端對象的 firstName域。

清單  /spittr/WebRoot/WEB-INF/views/registerForm.html:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" 
          th:href="{/resources/style.css}" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" ></link>
  </head>
  <body>
    <div id="header" th:include="page :: header"></div>

    <div id="content">
      <h1>Register</h1>
  
      <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>
    </div>
    <div id="footer" th:include="page :: copy"></div>
  </body>
</html>
           

運作結果:

SpringInAction筆記(六)—— 渲染Web視圖(下)
SpringInAction筆記(六)—— 渲染Web視圖(下)