學習SpringMVC——說說視圖解析器
各位前排的,后排的,都不要走,咱趁熱打鐵,就這一股勁我們今天來說說spring mvc的視圖解析器(不要搶,都有位子~~~)
相信大家在昨天那篇如何獲取請求參數篇中都已經領略到了spring mvc注解的魅力和套路了。搭上@RequestMapping的便車,我們可以去到我們想去的地方(方法)去,借助@RequestParam、@PathVariable等我們可以得到請求中想要的參數值,最終還能夠通過神奇的“return SUCCESS”到達我們的目的地。今天主要就來說說在達到目的地的路上,我們都經歷了些什么!
在此之前
我們順便說說@RequestHeader、請求參數類型為POJO(也就是Java對象類型)的情況以及ModelAndView
1. @RequestHeader
這個無需多說,還是原來的配方,還是一樣的套路,只要舉個例子,你就都明白了。
在SpringMVCTest中添加測試方法
1
2
3
4
5
|
@RequestMapping
(value=
"/testRequestHeader"
)
public
String testRequestHeader(
@RequestHeader
(value=
"Accept-Language"
) String language){
System.out.println(
"testRequestHeader Accept-Languge:"
+ language);
return
SUCCESS;
}
|
我們知道一個請求如get請求或post都有請求頭和響應頭,這里我們想獲取的是請求頭中“Accept-Language”的具體信息,所以就用上了@RequestHeader注解來獲取。
index.jsp中
1
|
<
a
href="springmvc/testRequestHeader">testRequestHeader</
a
><
br
/><
br
/>
|
啟動服務器,點擊超鏈接,我們得到了
1
|
testRequestHeader Accept-Languge:zh-CN
|
2. 請求參數為POJO
前面兩篇,我們看到的請求類型都是一些字符串也就是某一個字段。那么如果現在有一個form表單,說誇張點,表單中有10個字段需要提交,行吧,還用原來的匹配的方式,你要用10個參數來接收,累不累?累!有沒有辦法?有!我們可以把這些要提交的字段封裝在一個對象中,從而請求類型就是一個POJO。
這里我們新建一個類User
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
package
com.jackie.springmvc.entities;
public
class
User {
private
Integer id;
private
String username;
private
String password;
private
String email;
private
int
age;
private
Address address;
public
Integer getId() {
return
id;
}
public
void
setId(Integer id) {
this
.id = id;
}
public
String getUsername() {
return
username;
}
public
void
setUsername(String username) {
this
.username = username;
}
public
String getPassword() {
return
password;
}
public
void
setPassword(String password) {
this
.password = password;
}
public
String getEmail() {
return
email;
}
public
void
setEmail(String email) {
this
.email = email;
}
public
int
getAge() {
return
age;
}
public
void
setAge(
int
age) {
this
.age = age;
}
public
Address getAddress() {
return
address;
}
public
void
setAddress(Address address) {
this
.address = address;
}
public
User(String username, String password, String email,
int
age) {
super
();
this
.username = username;
this
.password = password;
this
.email = email;
this
.age = age;
}
public
User(Integer id, String username, String password, String email,
int
age) {
super
();
this
.id = id;
this
.username = username;
this
.password = password;
this
.email = email;
this
.age = age;
}
@Override
public
String toString() {
return
"User [id="
+ id +
", username="
+ username +
", password="
+ password +
", email="
+ email +
", age="
+ age +
"]"
;
}
public
User() {
}
}
|
還有一個Address類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package
com.jackie.springmvc.entities;
public
class
Address {
private
String province;
private
String city;
public
String getProvince() {
return
province;
}
public
void
setProvince(String province) {
this
.province = province;
}
public
String getCity() {
return
city;
}
public
void
setCity(String city) {
this
.city = city;
}
@Override
public
String toString() {
return
"Address [province="
+ province +
", city="
+ city +
"]"
;
}
}
|
同時我們還需要在SpringMVCTest中寫一個testPojo的測試方法
1
2
3
4
5
|
@RequestMapping
(value=
"/testPojo"
)
public
String testPojo(User user){
System.out.println(
"testPojo: "
+ user);
return
SUCCESS;
}
|
好了,這樣,我們就可以在前台jsp頁面上構造這樣的表單數據了
1
2
3
4
5
6
7
8
9
|
<
form
action="springmvc/testPojo" method="post">
username: <
input
type="text" name="username"><
br
>
password: <
input
type="password" name="password"><
br
>
email: <
input
type="text" name="email"><
br
>
age: <
input
type="text" name="age"><
br
>
city: <
input
type="text" name="address.city"><
br
>
province: <
input
type="text" name="address.province"><
br
>
<
input
type="submit" value="submit">
</
form
><
br
/><
br
/>
|
至此,我們啟動tomcat服務器,就可以發送一個POJO類型的參數了,並且我們成功了讀取了這個請求參數
3. ModelAndView
ModelAndView是什么鬼?其實它是我們經常寫在SpringMVCTest里測試方法的返回值類型,在方法體內我們可以通過ModelAndView對象來是像請求域中添加模型數據的,抽象?那就看例子吧~~~
SpringMVCTest中添加方法
1
2
3
4
5
6
7
|
@RequestMapping
(value=
"/testModelAndView"
)
public
ModelAndView testModelAndView(){
String viewname = SUCCESS;
ModelAndView modelAndView =
new
ModelAndView(viewname);
modelAndView.addObject(
"time"
,
new
Date());
return
modelAndView;
}
|
index.jsp中還是添加一個超鏈接
1
|
<
a
href="springmvc/testModelAndView">testModelAndView</
a
><
br
/><
br
/>
|
注意我們需要在結果頁面中拿到這個放入請求域中的鍵值對,所以在success.jsp頁面中添加
1
|
time: ${requestScope.time}<
br
><
br
>
|
最終的效果圖是這樣的
沒錯,我們將當前時間信息寫進了請求域,並通過視圖展示出來。
有了前面的小鋪墊,現在我們來嘮嘮這視圖解析器的事兒
視圖解析器
這里主要通過調試源代碼看看spring mvc的handler是如何利用視圖解析器找到並返回實際的物理視圖的,別眨眼
1. 如何看源碼
說到調試源碼,我們就要有源碼才行,那么如何看源碼,相信這個頁面大家已經看膩了吧
沒錯,這是因為你沒有導入源碼的jar包,程序沒辦法給你呈現源代碼,還好,這個問題難不倒我們,在第一篇中我們有關於springframework所需要的功能jar包,javadoc以及源碼包,那么來導入一波
選中前面提示的spring-context的source jar包,我們就可以一睹這個java文件的廬山真面目了
484很開心~~~
2. 代碼調試
為此我們寫一個測試方法
1
2
3
4
5
|
@RequestMapping
(
"/testViewAndViewResolver"
)
public
String testViewAndViewResolver(){
System.out.println(
"testViewAndViewResolver"
);
return
SUCCESS;
}
|
index.jsp加個鏈接
1
|
<
a
href="springmvc/testViewAndViewResolver">testViewAndViewResolver</
a
><
br
/><
br
/>
|
給testViewAndView方法體一個斷點,我們進入調試狀態,
程序停在斷點處,在調試的上下文中,我們找到DispatcherServlet.doDispaatch方法,以此為入口,來看看視圖解析器
(1) 進入DispatcherServlet.doDispaatch
定位到
1
|
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
|
可以看到這里有個mv對象,實際上就是ModelAndView,通過調試我們發現這里的mv中包括了model和view,view的指向就是success,而model這里之所以有值是因為在SpringMVCTest中有一個getUser方法,且加上了@ModelAttribute注解,從而初始化了model。
(2)執行processDispatchResult方法
在doDispatch中繼續執行,直到
1
|
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
|
進入該方法進行視圖渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
private
void
processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
throws
Exception {
boolean
errorView =
false
;
if
(exception !=
null
) {
if
(exception
instanceof
ModelAndViewDefiningException) {
logger.debug(
"ModelAndViewDefiningException encountered"
, exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else
{
Object handler = (mappedHandler !=
null
? mappedHandler.getHandler() :
null
);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv !=
null
);
}
}
// Did the handler return a view to render?
if
(mv !=
null
&& !mv.wasCleared()) {
render(mv, request, response);
if
(errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else
{
if
(logger.isDebugEnabled()) {
logger.debug(
"Null ModelAndView returned to DispatcherServlet with name '"
+ getServletName() +
"': assuming HandlerAdapter completed request handling"
);
}
}
if
(WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return
;
}
if
(mappedHandler !=
null
) {
mappedHandler.triggerAfterCompletion(request, response,
null
);
}
}
|
這里我們着重看下render方法,然后得到視圖的名字,即運行到view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);進入到該方法后,我們可以看到整個方法如下:
1
2
3
4
5
6
7
8
9
10
11
|
protected
View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request)
throws
Exception {
for
(ViewResolver viewResolver :
this
.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if
(view !=
null
) {
return
view;
}
}
return
null
;
}
|
這里用到了視圖解析器即this.viewResolvers。而真正的渲染視圖在DispatcherServlet的view.render(mv.getModelInternal(), request, response);點擊進入這里的render方法,我們選擇AbstractView這個抽象類中的該方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/**
* Prepares the view given the specified model, merging it with static
* attributes and a RequestContext attribute, if necessary.
* Delegates to renderMergedOutputModel for the actual rendering.
* @see #renderMergedOutputModel
*/
@Override
public
void
render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws
Exception {
if
(logger.isTraceEnabled()) {
logger.trace(
"Rendering view with name '"
+
this
.beanName +
"' with model "
+ model +
" and static attributes "
+
this
.staticAttributes);
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, request, response);
}
|
該方法負責針對具體的Model呈現具體的view,這時候再進入到renderMergedOutputMode的具體實現類
點擊后,我們發現對此方法多個類都有實現,那么到底是哪個呢,實際上是InternalResourceView這個類,為什么定位到這個類,筆者是根據之前在springmvc.xml中配置的視圖解析器的線索找到的,當時我們配的是InternalResourceViewResolver這個解析器,所以相應的,這里應該是InternalResourceView類,同時通過加斷點,更加驗證了這一想法~~~
此外在調試DispatcherServlet的resolveViewName方法時,發現,這里的viewResolver正是我們配置的視圖解析器InternalResourceViewResolver
同時發現這里返回的view就是/WEB-INF/views/success.jsp
至此,我們就完成了ModelAndView的邏輯路徑向這里"/WEB-INF/views/success.jsp"的物理路徑的轉化,大致了了解了視圖解析器的工作機制(感覺還是沒有說清楚--!)。
好了,本篇我們主要學習了
- @Request的用法
- 請求參數為POJO的用法
- ModelAndView的用法
- 如何看源代碼
- spring mvc如何通過視圖解析器得到真正的物理視圖頁面