《Spring實戰》讀書筆記-第6章 渲染Web視圖

本章內容:javascript

  • 將模型數據渲染爲HTMLcss

  • 使用JSP視圖html

  • 經過tiles定義視圖佈局java

  • 使用Thymeleaf視圖web

在《Spring實戰》的書中是包括6.3 使用Apache Tiles視圖定義佈局和6.4 使用Thymeleaf這兩個小節的,可是考慮到平時開發使用的比較少,就不進行介紹了,感興趣的小夥伴們能夠自行學習spring

上一章主要關注於如何編寫處理Web請求的控制器。咱們也建立了一些簡單的視圖,用來渲染控制器產生的模型數據,但咱們並無花太多時間討論視圖,也沒有討論控制器完成請求到結果渲染到用戶的瀏覽器中的這段時間內到底發生了什麼,而這正是本章的主要內容。typescript

6.1 理解視圖解析

Spring MVC定義了一個名爲ViewResolver的接口,它大體以下所示:swift

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中。安全

儘管咱們能夠編寫ViewResolver和View的實現,在有些特定的場景下,這樣作也是有必要的,但通常來說,咱們並不須要關心這些接口。Spring提供了多個內置的實現。以下表所示,Spring自帶了13個視圖解析器,可以將邏輯視圖名轉換爲物理實現。

Spring 4和Spring 3.2支持表6的全部視圖解析器。Spring 3.1支持除Tiles 3 TilesViewResolver以外的全部視圖解析器。

6.2 建立JSP視圖

Spring提供了兩種支持JSP視圖的方式:

  • InternalResourceViewResolver會將視圖名解析爲JSP文件。另外,若是在你的JSP頁面中使用了JSP標準標籤庫(JavaServer Pages Standard Tag Library, JSTL)的話,InternalResourceViewResolver可以將視圖名解析爲JstlView形式的JSP文件,從而將JSTL本地化和資源bundle變量暴露給JSTL的格式化(formatting)和信息(message) 標籤。

  • Spring提供了兩個JSP標籤庫,一個用於表單到模型的綁定,另外一個提供了通用的工具類特性。

無論使用JSTL,仍是準備使用Spring的JSP標籤庫,配置解析JSP的視圖解析器都是很是重要的。儘管Spring還有其餘的幾個視圖解析器都能將視圖名映射爲JSP文件,但就這項任務來說,InternalResourceViewResolver是最簡單和最經常使用的視圖解析器。

配置適用於JSP的視圖解析器

InternalResourceViewResolver遵循一種約定,會在視圖名上添加前綴和後綴,進而肯定一個Web應用中視圖資源的物理路徑。

通用的實踐是將JSP文件放到Web應用的WEB-INF目錄下,防止對它的直接訪問。假設邏輯視圖名爲home,那麼能夠肯定物理視圖的路徑就是邏輯視圖名home再加上「/WEB-INF/views/」前綴和「.jsp」後綴。

InternalResourceViewResolver解析視圖時,會在視圖名上添加前綴和後綴

當使用@Bean註解的時候,咱們能夠按照以下的方法配置InternalResourceViewResolver,使其在解析視圖時,遵循上述的約定。

@Beanpublic ViewResolver viewResolver(){ // 配置jsp視圖解析器 InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp");return resolver;}

做爲替代方案,若是更喜歡使用基於XML的Spring配置,那麼能夠按照以下的方式配置InternalResourceViewResolver

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/views/" p:suffix=".jsp" />

InternalResourceViewResolver配置就緒以後,它就會將邏輯視圖名解析爲JSP文件,以下所示:

  • home將會解析爲「/WEB-INF/views/home.jsp」

  • productList將會解析爲「/WEB-INF/views/productList.jsp」

  • books/detail將會解析爲「/WEB-INF/views/books/detail.jsp」

重點看一下最後一個樣例。當邏輯視圖名中包含斜線時,這個斜線也會帶到資源的路徑名中。所以,它會對應到prefix屬性所引用目錄的子目錄下的JSP文件。這樣的話,咱們就能夠很方便地將視圖模板組織爲層級目錄,而不是將它們都放到同一個目錄之中。

解析JSTL視圖

若是JSP使用JSTL標籤來處理格式化和信息的話,那麼咱們會但願InternalResourceViewResolver將視圖解析爲JstlView。

JSTL的格式化標籤須要一個Locale對象,以便於恰當地格式化地域相關的值,如日期和貨幣。信息標籤能夠藉助Spring的信息資源和Locale,從而選擇適當的信息渲染到HTML之中。經過解析JstlView,JSTL可以得到Locale對象以及Spring中配置的信息資源。

若是想讓InternalResourceViewResolver將視圖解析爲JstlView,而不是InternalResourceView的話,那麼咱們只需設置它的viewClass屬性便可:

@Beanpublic ViewResolver viewResolver(){ // 配置jsp視圖解析器 InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); 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/views/" p:suffix=".jsp" p:viewClass="org.springframework.web.servlet.view.JstlView" />

無論使用Java配置仍是使用XML,都能確保JSTL的格式化和信息標籤可以得到Locale對象以及Spring中配置的信息資源。

使用Spring的JSP庫

Spring提供了兩個JSP標籤庫,用來幫助定義Spring MVC Web的視圖。其中一個標籤庫會用來渲染HTML表單便籤,這些標籤能夠綁定model中的某個屬性。另一個標籤庫包含了一些工具類標籤,咱們隨時均可以很是便利地使用它們。

咱們將會看到如何將Spittr應用的註冊表單綁定到模型上,這樣表單就能夠預先填充值,而且在表單提交失敗後,可以展示校驗錯誤。

將表單綁定到模型上

Spring的表單綁定JSP標籤庫包含了14個標籤,它們中的大多數都用來渲染HTML中的表單標籤。可是,它們與原生HTML標籤的區別在於它們會綁定模型中的一個對象,可以根據模型中對象的屬性填充值。標籤庫中還包含了一個爲用戶展現錯誤的標籤,它會將錯誤信息渲染到最終的HTML之中。

爲了使用表單綁定庫,須要在JSP頁面中對其進行聲明:

<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form" %>

須要注意,咱們將前綴指定爲「sf」,但一般也可能使用「form」前綴,能夠自定義前綴。

在聲明完表單綁定標籤庫以後,你就可使用14個相關的便籤了。以下表

咱們在Spittr的樣例中,在註冊JSP中可使用<sf:form><sf:input><sf:password>WW

<sf:form method="POST" commandName="spitter" > First Name:<sf:input path="firstName"/><br/> Last Name:<sf:input path="lastName" /><br/> Email:<sf:input type="email" path="email" /><br/> Username: <sf:input path="username"/><br/> Password: <sf:password path="password"/><br/><input type="submit" value="Register" /></sf:form>

<sf:form>會渲染一個HTML <form>標籤,但它也會經過commandName屬性構建針對某個模型對象的上下文信息。在其餘的表單綁定標籤中,會引用這個模型對象的屬性。

在以前的代碼中,咱們將commandName屬性設置爲spitter。所以,在模型中必需要有一個key爲spitter的對象,不然的話,表單不能正常渲染(會出現JSP錯誤)。這意味着咱們須要修改一下SpitterController,以確保模型中存在以spitter爲key的Spitter對象:

@RequestMapping(value="/register", method=GET)public String showRegistrationForm(Model model) { model.addAttribute(new Spitter()); return "registerForm";}

修改後,模型中的key根據對象類型推斷得出spitter就是新增的Spitter實例。

回到這個表單中,前四個輸入域將HTML<input>標籤改爲了<sf:input>。這個標籤會渲染成一個HTML <input>標籤,而且type屬性將會設置爲text。咱們在這裏設置了path屬性,<input>標籤的value屬性值將會設置爲模型對象中path屬性所對應的值。

對於password輸入域,咱們使用<sf:password>來代替<sf:input><sf:password><sf:input>相似,可是它所渲染的HTML <input>標籤中,會將type屬性爲password,這樣當輸入的時候,它的值不會直接明文顯示。

值得注意的是,從Spring 3.1開始,<sf:input>標籤可以容許咱們指定type屬性,這樣的話,除了其餘可選的類型外,還能指定HTML5特定類型的文本域,如date、range和email。

Email: <sf:input path="email" type="email" /><br/>

爲了指導用戶矯正錯誤,咱們須要使用<sf:errors>

若是存在校驗錯誤的話,請求中會包含錯誤的詳細信息,這些信息是與模型數據放到一塊兒的。咱們所須要作的就是到模型中將這些數據抽取出來,並展示給用戶。

<sf:form method="POST" commandName="spitter"> First Name: <sf:input path="firstName" /><sf:errors path="firstName"/><br/>...</sf:form>

儘管值展現了將<sf:errors>用到First Name輸入域的場景,可是它能夠按照一樣簡單的方式用到註冊表單的其餘輸入域中。在這裏,它的path屬性設置成了firstName,也就是指定了要顯示Spitter模型對象中哪一個屬性的錯誤。若是firstName屬性沒有錯誤的話,那麼<sf:errors>不會渲染任何內容。但若是有校驗錯誤的話,那麼將會在HTML<span>標籤中顯示錯誤信息。

如今,咱們已經能夠爲用戶展示錯誤信息,這樣他們就能修正這些錯誤了。咱們還能夠修改錯誤的樣式,使其更加突出顯示。爲了作到這一點,能夠設置cssClass屬性:

<sf:form method="POST" commandName="spitter"> First Name: <sf:input path="firstName" /><sf:errors path="firstName" cssClass="error"/><br/>...</sf:form>

定義這個css樣式

span.error {color: red;}

展現顯示結果

在表單輸入域的旁邊展示校驗錯誤信息

除了這種方式,還有另外一種處理校驗錯誤方式就是將全部的錯誤信息在同一個地方進行顯示。爲了作到這一點,咱們能夠移除每一個輸入域上的<sf:errors>元素,並將其放到表單的頂部

<sf:form method="POST" commandName="spitter"><sf:errors path="*" element="div" cssClass="error"/>...</sf:form>

跟以前相比,值得注意的不一樣之處在於它的path被設置成了「*」。這是一個通配符選擇器,會告訴<sf:errors>展示全部屬性的全部錯誤。

一樣須要注意的是,咱們將element屬性設置成了div。默認狀況下,錯誤都會渲染在HTML 標籤中,若是隻顯示一個錯誤的話,這是不錯的選擇。可是,若是要渲染全部輸入域的錯誤的話,極可能要展示不止一個錯誤,這時候使用<span>標籤(行內元素)就不合適了。像<div>這樣的塊級元素會更爲合適。所以,咱們能夠將element屬性設置成了div。

像以前同樣,cssClass屬性被設置errors,這樣咱們就能爲<div>設置樣式了。

div.errors {background-color: #ffcccc;border: 2px solid red;}

如今,咱們在表單的上方顯示全部的錯誤,這樣頁面佈局可能會更加容易一些。可是,咱們尚未着重顯示須要修正的輸入域。經過爲每一個輸入域設置cssErrorClass屬性,這個問題很容易解決。咱們也能夠將每一個label都替換爲<sf: label>,並設置它的cssErrorClass屬性。以下就是作完必要修改後的FirstName輸入域:

<sf:form method="POST" commandName="spitter"><sf:label path="firstName" cssErrorClass="error">First Name</sf:label>: <sf:input path="firstName" cssErrorClass="error" /><br/>...</sf:form>

<sf:label>標籤像其餘的表單綁定標籤同樣,使用path來指定它屬於模型對象中的哪一個屬性。在本例中,咱們將其設置爲firstName,所以它會綁定Spitter對象的firstName屬性。假設沒有校驗錯誤的話,它將會渲染爲以下的HTML <label>元素:

<label for="firstName">First Name</label>

就其自身來講,設置<sf:label>的path屬性並無完成太多的功能,可是,咱們還同時設置了cssErrorClass屬性。若是它所綁定的屬性有任何錯誤的話,在渲染獲得的<label>元素中,class屬性將會被設置爲 error,以下所示:

<label for="firstName" class="error">First Name</label>

與之相似,<sf:input>標籤的cssErrorClass屬性被設置爲error,若是有任何校驗錯誤,class屬性將會被設置爲error。下面設置css

label.error {color: red;}
input.error {background-color: #ffcccc;}

爲了讓這些錯誤信息更加易讀,咱們從新改造Spitter類

@NotNull@Size(min=5, max=16,message = "{username.size}")private String username;
@NotNull@Size(min=5, max=25,message = "{password.size}")private String password;
@NotNull@Size(min=2, max=30,message = "{firstName.size}")private String firstName;
@NotNull@Size(min=2, max=30,message = "{lastName.size}")private String lastName;
@NotNull@Email(message = "{email.valid}")private String email;

對於上面每一個域,咱們都將其@Size註解的messgae設置爲一個字符串,這個字符串是用大括號括起來的。若是沒有大括號的話,message中的值將會做爲展示給用戶的錯誤信息。可是使用了大括號以後,咱們使用的就是屬性文件中的某一屬性,該屬性包含了實際的信息。

接下來須要作的就是建立一個名爲ValidationMessage.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.username.size=Username must be between {min} and {max} characters long.password.size=Password must be between {min} and {max} characters long.email.valid=The email address must be valid.

ValidationMessage.properties文件中天天信息的key值對應於註解中message屬性佔位符的值。同時,最小和最大長度以佔位符的方式({min}和{max})保存文件中,它們會引用@Size註解上所設置的min和max屬性。

當用戶提交的註冊表單校驗失敗的話,他們在瀏覽器中應該能夠看到以下界面。

顯示校驗錯誤,其中這些對用戶友好的信息是從屬性文件中獲取到的

咱們能夠按需建立任意數量的ValidationMessage.properties文件,使其涵蓋咱們想支持的全部語言和地域。

Spring通用的標籤庫

除了表單綁定標籤庫以外,Spring還提供了更爲通用的JSP標籤庫。

要使用Spring通用的標籤庫,咱們必需要在頁面上對其進行聲明:

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>

標籤庫聲明以後,咱們就可使用下表的十個JSP標籤了。

展示國際化信息

若是要修改JSP模板中的文本,就不那麼容易,並且,沒有辦法根據用戶的語言設置國際化這些文本。
例如:

<h1>Welcome to Spitter!</h1>

若是想把其中的文本作成國際化的版本,對於渲染文原本說,是很好的方案,文本可以位於一個或多個屬性文件中。藉助<s:message>,咱們能夠將硬編碼的歡迎信息替換爲以下的形式:

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

按照這裏的方式,<s:message>將會根據key爲spitter.welcome的信息源來渲染文本。

Spring有多個信息源的類,它們都實現了MessageSource接口。在這些類中,更爲常見和有用的是ResourceBundleMessageSource。它會從一個屬性文件中加載信息,這個屬性文件的名稱是根據基礎名稱(base name)衍生而來的。以下的@Bean方法配置了ResourceBundleMessageSource:

@Beanpublic MessageSource messageSource(){ ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("message"); messageSource.setDefaultEncoding("UTF-8");return messageSource;}

在這個bean聲明中,核心在於設置basename屬性。你能夠將其設置爲任意你喜歡的值,在這裏,我將其設置爲message。將其設置爲message後,ResourceBundleMessageSource就會試圖在根路徑的屬性文件中解析信息,這些屬性文件的名稱是根據這個基礎名稱衍生獲得的。

另外的可選方案是使用ReloadableResourceBundleMessageSource,它的工做方式與ResourceBundleMessageSource很是相似,可是它可以從新加載信息屬性,而沒必要從新編譯或重啓應用。以下是配置ReloadableResourceBundleMessageSource的樣例:

@Beanpublic MessageSource messageSource(){ ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setBasename("file:///etc/spittr/messages"); messageSource.setCacheSeconds(10);return messageSource;}

這裏的關鍵區別在於basename屬性設置爲在應用的外部查找。basename屬性能夠設置爲類路徑下(以「classpath:」做爲前綴)、文件系統中(以「file:」做爲前綴)或Web應用的根路徑下(沒有前綴)查找屬性。

如今,咱們來建立這些屬性文件。首先建立默認的屬性文件,名爲message.properties。它要們位於根類路徑下(若是使用ResourceBundleMessageSource的話),要麼位於basename屬性指定的路徑下(若是使用ReloadableResourceBundleMessageSource的話)。對spittr.welcome信息來說,它須要以下的條目:

spittr.welcome=Welcome to Spittr!

咱們已經具有了對信息進行國際化的重要組成部分。例如,若是想要爲語言設置爲西班牙語的用戶展現西班牙語的歡迎信息,那麼須要建立另一個名爲message_es.properties的屬性文件,幷包含以下的條目:

spittr.welcome=Bienvenidos a Spittr!

建立URL

<s:url>的主要任務就是建立URL,而後將其賦值給一個變量或者渲染到響應中。它是JSTL中<c:url>標籤的替代者,可是它具有幾項特殊的技巧。

<s:url>會接受一個相對Servlet上下文的URL,並在渲染的時候,預先添加上Servlet上下文路徑。例如,考慮以下<s:url>的基本用法:

<a href="<s:url href="/spitter/register" />">Register</a>

若是應用的Servlet上下文名爲spittr,那麼在響應中將會渲染以下的HTML:

<a href="/spittr/spitter/register">Register</a>

這樣,咱們在建立URL的時候,就沒必要再擔憂Servlet上下文路徑是什麼了,<s:url>將會負責這件事。

另外,咱們還可使用<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>使用兩個內嵌的<s:param>標籤,來設置「/spittles」的max和count參數:

<s:url href="/spittles" var="spittlesUrl"><s:param name="max" value="60" /><s:param name="count" value="20" /></s:url>

若是咱們須要建立帶有路徑(path)參數的URL,咱們該如何設置》

例如,假設咱們須要爲特定用戶的基本信息頁面建立一個URL。那沒有問題,<s:param>標籤能夠承擔此任務:

<s:url href="/spitter/{username}" var="spittlesUrl"><s:param name="username" value="jbauer" /></s:url>

當href屬性中的佔位符匹配<s:param>中所指定的參數時,這個參數將會插入到佔位符的位置中。若是<s:param>參數沒法匹配href中的任何佔位符,那麼這個參數將會做爲查詢參數。

<s:url>標籤還能夠解決URL的轉義需求。例如,若是你但願將渲染獲得的URL內容展示在Web頁面上(而不是做爲超連接),那麼你應該要求<s:url>進行HTML轉義,這須要將htmlEscape屬性設置爲true。例如,以下<s:url>將會渲染HTML轉義後的URL:

<s:url href="/spittles" htmlEscape="true"><s:param name="max" value="60" /><s:param name="count" value="20" /></s:url>

所渲染的URL結果以下所示:

/spitter/spittles?max=60&amp;count=20

另外一方面,若是你但願在JavaScript代碼中使用URL的話,那麼應該將javaScriptEscape屬性設置爲true:

<s:url href="/spittles" var="spittlesJSUrl" javaScriptEscape="true"><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>

轉義內容

<s:escapeBody>標籤是一個通用的轉義標籤。它會渲染標籤體中內嵌的內容,而且在必要的時候進行轉義。

例如,假設你但願在頁面上展示一個HTML代碼片斷。爲了正確顯示,咱們須要將「<」和「>」字符替換爲「<」和「>」,不然的話,瀏覽器將會像解析頁面上其餘HTML那樣解析這段HTML內容。

固然,沒有人禁止咱們手動將其轉義爲「<」和「>」,可是這很繁瑣,而且代碼難以閱讀。咱們可使用<s:escapeBody>,並讓Spring完成這項任務:

<s:escapeBody htmlEscape="true"><h1>Hello</h1></s:escapeBody>

它將會在響應體中渲染成以下的內容:

&lt;h1&gt;Hello&lt;/h1&gt;

雖然轉義後的格式看起來很難讀,但瀏覽器能夠將其轉換爲未轉義的HTML。

經過設置javaScriptEscape屬性,<s:escapeBody>標籤還支持JavaScript轉義:

<s:escapeBody javaScriptEscape="true"><h1>Hello</h1></s:escapeBody>

<s:escapeBody>只完成一件事,而且完成得很是好。與<s:url>不一樣,它只會渲染內容,並不能將內容設置爲變量。

6.3 小結

處理請求只是Spring MVC功能的一部分。若是控制器所產生的結果想要讓人看到,那麼它們產生的模型數據就要渲染到視圖中,並展示到用戶的Web瀏覽器中。Spring的視圖渲染是很靈活的,並提供了多個內置的可選方案,包括傳統的JavaServer Pages以及流行的Apache Tiles佈局引擎。

在本章中,咱們首先快速瞭解了一下Spring所提供的視圖和視圖解析可選方案。咱們還深刻學習瞭如何在Spring MVC中使用JSP和Apache Tiles。

咱們還看到了如何使用Thymeleaf做爲Spring MVC應用的視圖層,它被視爲JSP的替代方案。Thymeleaf是一項頗有吸引力的技術,由於它能建立原始的模板,這些模板是純HTML,能像靜態HTML那樣以原

始的方式編寫和預覽,而且可以在運行時渲染動態模型數據。除此以外,Thymeleaf是與Servlet沒有耦合關係的,這樣它就可以用在JSP所不能使用的領域中。

Spittr應用的視圖定義完成以後,咱們已經具備了一個雖然微小可是可部署且具備必定功能的Spring MVC Web應用。還有一些其餘的特性須要更新進來,如數據持久化和安全性,咱們會在合適的時候關注這些特性。但如今,這個應用開始變得有模有樣了。

在深刻學習應用的技術棧以前,在下一章咱們將會繼續討論Spring MVC,學習這個框架中一些更爲有用和高級的功能。

原文連接:

https://thinkwon.blog.csdn.net/article/details/103559672

本文分享自微信公衆號 - 源代碼社區(ydmsq666)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索