一、理解視圖解析
在之前的講解中,我們使用名為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
接口的任務就是接受模型以及Servlet
的request
和response
對象,并將輸出結果渲染到response
中。看起來非常簡單,我們只需要編寫上述兩個接口的實現即可,有時候也需要這樣做,但是一般來講,并不需要,Spring
提供了多個內置的實現。
Spring
自帶了十三個視圖解析器,能夠將邏輯視圖名轉換為物理實現。
視圖解析器 | 描述 |
---|---|
BeanNameViewResolver |
將視圖解析為Spring 應用上下文中的bean ,其中bean 的ID 與視圖的名字相同 |
ContentNegotiatingViewResovler |
通過考慮客戶端需要的內容類型來解析視圖,委托給另外一個能夠產生對應內容類型的視圖解析器 |
FreeMarkerViewResolver |
將視圖解析為FreeMarker 模版 |
InternalResourceViewResolver |
將視圖解析為Web 應用的內部資源(一般為JSP ) |
JasperReportsViewResolver |
將視圖解析為JasperReports 定義 |
ResourceBundleViewResolver |
將視圖解析為資源bundle (一般為屬性文件) |
TilesViewResolver |
將視圖解析為Apache Tile 定義,其中tile ID 與視圖名稱相同,注意有兩個不同的TilesViewResolver 實現,分別對應于Tiles 2.0 和Tiles 3.0
|
UrlBasedViewResolver |
直接根據視圖的名稱解析視圖,視圖的名稱會匹配一個物理視圖的定義 |
VelocityLayoutViewResolver |
將視圖解析為Velocity 布局,從不同的Velocity 模版中組合頁面 |
VelocityViewResolver |
將視圖解析為Velocity 模版 |
XmlViewResolver |
將視圖解析為特定的XML 文件中的bean 定義,類似于BeanNameViewResolver
|
XsltViewResolver |
將視圖解析為XSLT 轉換后的結果 |
說明:Spring 4
和Spring 3.2
支持表中所有的視圖解析器。Spring 3.1
支持除了Tiles 3 TilesViewResolver
之外的所有視圖解析器。這里InternalResourceViewResolver
一般會用于JSP
,TilesViewResolver
用于Apache Tiles
視圖,而FreeMarkerViewResolver
和VelocityViewResolver
分別對應FreeMarker
和Velocity
模版視圖。
二、創建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
將視圖解析為JstlView
。JSTL
的格式化標簽需要一個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
,因此,在模型中必須要有一個key
為spitter
的對象,否則的話,表單不能正常渲染。下面看如何確保模型中存在key
為spitter
的對象(在控制器中設置):
@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、range
和email
。
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>
會有一個值為error
的class
屬性,于是可以為這個類定義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
注解上所設置的min
和max
屬性。