學習SpringMVC——說說視圖解析器


  各位前排的,后排的,都不要走,咱趁熱打鐵,就這一股勁我們今天來說說spring mvc的視圖解析器(不要搶,都有位子~~~)

  相信大家在昨天那篇如何獲取請求參數篇中都已經領略到了spring mvc注解的魅力和套路了。搭上@RequestMapping的便車,我們可以去到我們想去的地方(方法)去,借助@RequestParam、@PathVariable等我們可以得到請求中想要的參數值,最終還能夠通過神奇的“return SUCCESS”到達我們的目的地。今天主要就來說說在達到目的地的路上,我們都經歷了些什么!

 

在此之前

  我們順便說說@RequestHeader、請求參數類型為POJO(也就是Java對象類型)的情況以及ModelAndView

  1. @RequestHeader

  這個無需多說,還是原來的配方,還是一樣的套路,只要舉個例子,你就都明白了。

  在SpringMVCTest中添加測試方法

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

<a href="springmvc/testRequestHeader">testRequestHeader</a><br/><br/>

 

  啟動服務器,點擊超鏈接,我們得到了

testRequestHeader Accept-Languge:zh-CN

 

  2. 請求參數為POJO

  前面兩篇,我們看到的請求類型都是一些字符串也就是某一個字段。那么如果現在有一個form表單,說誇張點,表單中有10個字段需要提交,行吧,還用原來的匹配的方式,你要用10個參數來接收,累不累?累!有沒有辦法?有!我們可以把這些要提交的字段封裝在一個對象中,從而請求類型就是一個POJO。

  這里我們新建一個類User

 

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類

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的測試方法

@RequestMapping(value="/testPojo")
public String testPojo(User user){
	System.out.println("testPojo: " + user);
	return SUCCESS;
}

  

  好了,這樣,我們就可以在前台jsp頁面上構造這樣的表單數據了

<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中添加方法

@RequestMapping(value="/testModelAndView")
public ModelAndView testModelAndView(){
	String viewname = SUCCESS;
	ModelAndView modelAndView = new ModelAndView(viewname);
	modelAndView.addObject("time", new Date());
	return modelAndView;
}

 

  index.jsp中還是添加一個超鏈接

<a href="springmvc/testModelAndView">testModelAndView</a><br/><br/>

 

  注意我們需要在結果頁面中拿到這個放入請求域中的鍵值對,所以在success.jsp頁面中添加

time: ${requestScope.time}<br><br>

  

  最終的效果圖是這樣的

  沒錯,我們將當前時間信息寫進了請求域,並通過視圖展示出來。

 

  有了前面的小鋪墊,現在我們來嘮嘮這視圖解析器的事兒

  視圖解析器

  這里主要通過調試源代碼看看spring mvc的handler是如何利用視圖解析器找到並返回實際的物理視圖的,別眨眼

  1. 如何看源碼

  說到調試源碼,我們就要有源碼才行,那么如何看源碼,相信這個頁面大家已經看膩了吧

 

  沒錯,這是因為你沒有導入源碼的jar包,程序沒辦法給你呈現源代碼,還好,這個問題難不倒我們,在第一篇中我們有關於springframework所需要的功能jar包,javadoc以及源碼包,那么來導入一波

 

  選中前面提示的spring-context的source jar包,我們就可以一睹這個java文件的廬山真面目了

                                            484很開心~~~

 

  2. 代碼調試

  為此我們寫一個測試方法

@RequestMapping("/testViewAndViewResolver")
public String testViewAndViewResolver(){
	System.out.println("testViewAndViewResolver");
	return SUCCESS;
}

 

  index.jsp加個鏈接

<a href="springmvc/testViewAndViewResolver">testViewAndViewResolver</a><br/><br/>

 

  給testViewAndView方法體一個斷點,我們進入調試狀態,

 

  程序停在斷點處,在調試的上下文中,我們找到DispatcherServlet.doDispaatch方法,以此為入口,來看看視圖解析器

  (1) 進入DispatcherServlet.doDispaatch

  定位到

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

 

  可以看到這里有個mv對象,實際上就是ModelAndView,通過調試我們發現這里的mv中包括了model和view,view的指向就是success,而model這里之所以有值是因為在SpringMVCTest中有一個getUser方法,且加上了@ModelAttribute注解,從而初始化了model。

 

  (2)執行processDispatchResult方法

  在doDispatch中繼續執行,直到

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

 

  進入該方法進行視圖渲染

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);進入到該方法后,我們可以看到整個方法如下:

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這個抽象類中的該方法

 

/**
	 * 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如何通過視圖解析器得到真正的物理視圖頁面

 

  如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。(點贊不迷路,博主帶你上高速~~~)

 

 


免責聲明!

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



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