SpringMVC——說說視圖解析器


學習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"的物理路徑的轉化,大致了了解了視圖解析器的工作機制(感覺還是沒有說清楚--!)。

 

  好了,本篇我們主要學習了

  1. @Request的用法
  2. 請求參數為POJO的用法
  3. ModelAndView的用法
  4. 如何看源代碼
  5. spring mvc如何通過視圖解析器得到真正的物理視圖頁面


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM