15、渲染Web視圖(1)(Spring筆記)

一、理解視圖解析

在之前的講解中,我們使用名為InternalResourceViewResolver的視圖解析器。在它的配置中,為了得到視圖的名字,會使用“/WEB-INF/views/”前綴和“.jsp”后綴,從而確定來渲染模型的JSP文件的物理位置。現在回過頭來看看視圖解析的基礎知識以及Spring提供的其他視圖解析器。

Spring MVC定義了一個名為ViewResolver的接口:

public interface ViewResolver{
    View resolveViewName(String viewName, Locale locale) throw Exception;
}

此方法傳入一個視圖名和Locale對象時,它會返回一個View實例。View是另外一個接口:

public interface View{
    String getContentType();
    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throw Exception;
}

說明:View接口的任務就是接受模型以及Servletrequestresponse對象,并將輸出結果渲染到response中。看起來非常簡單,我們只需要編寫上述兩個接口的實現即可,有時候也需要這樣做,但是一般來講,并不需要,Spring提供了多個內置的實現。

Spring自帶了十三個視圖解析器,能夠將邏輯視圖名轉換為物理實現。

視圖解析器 描述
BeanNameViewResolver 將視圖解析為Spring應用上下文中的bean,其中beanID與視圖的名字相同
ContentNegotiatingViewResovler 通過考慮客戶端需要的內容類型來解析視圖,委托給另外一個能夠產生對應內容類型的視圖解析器
FreeMarkerViewResolver 將視圖解析為FreeMarker模版
InternalResourceViewResolver 將視圖解析為Web應用的內部資源(一般為JSP
JasperReportsViewResolver 將視圖解析為JasperReports定義
ResourceBundleViewResolver 將視圖解析為資源bundle(一般為屬性文件)
TilesViewResolver 將視圖解析為Apache Tile定義,其中tile ID與視圖名稱相同,注意有兩個不同的TilesViewResolver實現,分別對應于Tiles 2.0Tiles 3.0
UrlBasedViewResolver 直接根據視圖的名稱解析視圖,視圖的名稱會匹配一個物理視圖的定義
VelocityLayoutViewResolver 將視圖解析為Velocity布局,從不同的Velocity模版中組合頁面
VelocityViewResolver 將視圖解析為Velocity模版
XmlViewResolver 將視圖解析為特定的XML文件中的bean定義,類似于BeanNameViewResolver
XsltViewResolver 將視圖解析為XSLT轉換后的結果

說明:Spring 4Spring 3.2支持表中所有的視圖解析器。Spring 3.1支持除了Tiles 3 TilesViewResolver之外的所有視圖解析器。這里InternalResourceViewResolver一般會用于JSPTilesViewResolver用于Apache Tiles視圖,而FreeMarkerViewResolverVelocityViewResolver分別對應FreeMarkerVelocity模版視圖。

二、創建JSP視圖

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

  • InternalResourceViewResolver會將視圖名解析為JSP文件。另外,如果在JSP頁面中使用了JSTL,此解析器會將視圖名解析為JstlView形式的JSP文件,從而將JSTL本地化好資源bundle變量暴露給JSTL的格式化和信息標簽。

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

2.1 配置適用于JSP的視圖解析器

有一些視圖解析器,如ResourceBundleViewResovler會直接將邏輯視圖名映射為特定的View接口實現,而InternalResourceViewResolver所采取的方式并不那么直接。它遵循一種約定,會在視圖名上添加前綴和后綴,進而確定一個Web應用中視圖資源的物理路徑(前面一節已經說明,這里不再細說)。

在之前的13講中的WebConfig配置類中是這樣配置視圖解析器的:

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

說明:當然我們也可以使用XML進行配置:

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

說明:解析器配置好之后,它就會將邏輯視圖名解析為JSP文件,如下所示:

  • home將會解析為“/WEB-INF/views/home.jsp”
  • productList將會解析為“/WEB-INF/views/productList.jsp”
  • books/detail將會解析為“/WEB-INF/views/books/detail.jsp”

2.1.1 解析JSTL視圖

上述對視圖解析器的配置都很基礎。如果一些JSP使用JSTL標簽來處理格式化和信息的話,那么我們會希望InternalResourceViewResolver將視圖解析為JstlViewJSTL的格式化標簽需要一個Locale對象,以便于恰當地格式化地域相關的值,如日期和貨幣。信息標簽可以借助Spring的信息資源和Locale,從而選擇適當的信息渲染到HTML中。

如果想讓InternalResourceViewResolver將視圖解析為JstlView,而不是InternalResourceView的話,只需要設置它的viewClass屬性即可:

@Bean
public 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"/>

2.2 使用 Spring 的 JSP 庫

當為JSP添加功能時,標簽庫是一種很強大的方式,能夠避免在腳本塊中直接編寫Java代碼。Spring提供了兩個JSP標簽庫,用來幫助定義Spring MVC Web的視圖。其中一個標簽庫會用來渲染HTML表單標簽,這些標簽可以綁定model中的某個屬性。另外一個標簽庫包含了一些工具類標簽。

2.2.1 將表單綁定到模型上

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

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

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

借助Spring表單綁定標簽庫中所包含的標簽,能夠將模型對象綁定到渲染后的HTML

JSP標簽 描述
<sf:checkbox> 渲染成一個HTML <input>標簽,其中type屬性設置為checkbox
<sf:checkboxes> 渲染成多個HTML <input>標簽,其中type屬性設置為checkbox
<sf:errors> 渲染成一個HTML <span>中渲染輸入域的錯誤
<sf:form> 渲染成一個HTML <form>標簽,并為其內部標簽暴露綁定路徑,用于數據綁定
<sf:hidden> 渲染成一個HTML <input>標簽,其中type屬性設置為hidden
<sf:input> 渲染成一個HTML <input>標簽,其中type屬性設置為text
<sf:label> 渲染成一個HTML <label>標簽
<sf:option> 渲染成一個HTML <option>標簽,其中selected屬性根據所綁定的值進行設置
<sf:options> 按照綁定的集合、數組或Map,渲染成一個HTML <option>標簽的列表
<sf:password> 渲染成一個HTML <input>標簽,其中type屬性設置為password
<sf:radiobutton> 渲染成一個HTML <input>標簽,其中type屬性設置為radio
<sf:radiobuttons> 渲染成多個HTML <input>標簽,其中type屬性設置為radio
<sf:select> 渲染成一個HTML <select>標簽
<sf:textarea> 渲染成一個HTML <textarea>標簽

下面利用上述標簽構建注冊表單:

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

說明:<sf:form>會渲染一個HTML <form>標簽,但它也會通過commandName屬性構建針對某個對象的上下文信息。在其他的表單綁定標簽中,會引用這個模型對象的屬性。這里我們將commandName屬性設置為spitter,因此,在模型中必須要有一個keyspitter的對象,否則的話,表單不能正常渲染。下面看如何確保模型中存在keyspitter的對象(在控制器中設置):

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

說明:修改后,模型中的key是根據對象類型推斷得到的,也就是spitter

使用Spring標簽庫后最終的<from>元素如下:

<form id="spitter" action="/spitter/spitter/register" method="POST">
    First Name:<input id="firstName" name="firstName" type="text" value="Jack" />
    ...
</form>

可以看到會自動幫我們填寫相關屬性。而從Spring 3.1開始,<sf:input>標簽能夠允許我們指定type屬性,還能指定HTML 5特定類型的文本域,如date、rangeemail

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

這樣所渲染得到的HTML如下:

Email:<input id="email" name="email" type="email" value="jack"/>

這里value指定默認值。

2.2.2 展現錯誤

如果存放在校驗錯誤的話,請求中會包含錯誤的詳細信息,這些信息是與模型數據放在一起的。這里使用<sf:errors>標簽讓錯誤抽取出來顯示。如將此標簽用到registerForm.jsp中的代碼片段:

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

此時如果有校驗錯誤的話,那么它將會在一個HTML <span>標簽中顯示錯誤信息。此時得到的渲染的HTML為:

<form id="spitter" action="/spitter/spitter/register" method="POST">
    First Name:<input id="firstName" name="firstName" type="text" value="Jack" />
    <span id="firstName.errors">size must be between 2 and 30</span>
</form>

可以更近一步,修改錯誤的樣式,使其更加突出顯示。為了做到這一點,可以設置cssClass屬性:

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

這樣error<span>會有一個值為errorclass屬性,于是可以為這個類定義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,默認情況下是span標簽。因為需要一下子顯示多個錯誤信息,使用span標簽(行內元素)就不合適了。此時設置樣式如下:

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

現在,我們在表單的上方顯示所有的錯誤,這樣頁面布局可能會更加容易一些。但是,我們還沒有著重顯示需要修正的輸入域。通過為每個輸入域設置cssErrorClass屬性,這里可以將每個label都替換為<sf:label>,并設置它的cssErrorClass屬性:

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

說明:<sf:label>標簽像其他的表單綁定標簽一樣,使用path來指定它屬于模型對象中的哪個屬性。于是它會渲染為如下的HTML <label>元素:

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

但是設置<sf:label>path屬性并沒有完成太多的功能,但是我們還設置了cssErrorClass屬性。于是渲染如下:

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

現在,我們還可以讓錯誤信息更加易讀,在Spitter類可以加上message屬性:

@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;

接下來需要做的就是創建一個名為ValidationMessages.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.

這里的占位符{min}、{max}會引用@Size注解上所設置的minmax屬性。

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

推薦閱讀更多精彩內容