Spring MVC學習(四)-------Controller接口控制器詳解1

4.一、Controller簡介

Controller控制器,是MVC中的部分C,爲何是部分呢?由於此處的控制器主要負責功能處理部分:java

一、收集、驗證請求參數並綁定到命令對象;web

二、將命令對象交給業務對象,由業務對象處理並返回模型數據;算法

三、返回ModelAndView(Model部分是業務對象返回的模型數據,視圖部分爲邏輯視圖名)。spring

 

還記得DispatcherServlet嗎?主要負責總體的控制流程的調度部分:chrome

一、負責將請求委託給控制器進行處理;瀏覽器

二、根據控制器返回的邏輯視圖名選擇具體的視圖進行渲染(並把模型數據傳入)。緩存

 

所以MVC中完整的C(包含控制邏輯+功能處理)由(DispatcherServlet + Controller)組成。服務器

 

所以此處的控制器是Web MVC中部分,也能夠稱爲頁面控制器、動做、處理器。網絡

 

Spring Web MVC支持多種類型的控制器,好比實現Controller接口,從Spring2.5開始支持註解方式的控制器(如@Controller、@RequestMapping、@RequestParam、@ModelAttribute等),咱們也能夠本身實現相應的控制器(只須要定義相應的HandlerMapping和HandlerAdapter便可)。session

 

由於考慮到還有部分公司使用繼承Controller接口實現方式,所以咱們也學習一下,雖然已經不推薦使用了。

 

對於註解方式的控制器,後邊會詳細講,在此咱們先學習Spring2.5之前的Controller接口實現方式。

 

首先咱們將項目springmvc-chapter2複製一份改成項目springmvc-chapter4,本章示例將放置在springmvc-chapter4中。

你們須要將項目springmvc-chapter4/ .settings/ org.eclipse.wst.common.component下的chapter2改成chapter4,不然上下文仍是「springmvc-chapter2」。之後的每個章節都須要這麼作。

4.二、Controller接口

package org.springframework.web.servlet.mvc; public interface Controller {        ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; }

 

這是控制器接口,此處只有一個方法handleRequest,用於進行請求的功能處理,處理完請求後返回ModelAndView(Model模型數據部分 和 View視圖部分)。

 

還記得第二章的HelloWorld嗎?咱們的HelloWorldController實現Controller接口,Spring默認提供了一些Controller接口的實現以方便咱們使用,具體繼承體系如圖4-1:

 

圖4-1

4.三、WebContentGenerator

用於提供如瀏覽器緩存控制、是否必須有session開啓、支持的請求方法類型(GET、POST等)等,該類主要有以下屬性:

 

Set<String>   supportedMethods:設置支持的請求方法類型,默認支持「GET」、「POST」、「HEAD」,若是咱們想支持「PUT」,則能夠加入該集合「PUT」。

boolean requireSession = false:是否當前請求必須有session,若是此屬性爲true,但當前請求沒有打開session將拋出HttpSessionRequiredException異常;

 

boolean useExpiresHeader = true:是否使用HTTP1.0協議過時響應頭:若是true則會在響應頭添加:「Expires:」;須要配合cacheSeconds使用;

 

boolean useCacheControlHeader = true:是否使用HTTP1.1協議的緩存控制響應頭,若是true則會在響應頭添加;須要配合cacheSeconds使用;

 

boolean useCacheControlNoStore = true:是否使用HTTP 1.1協議的緩存控制響應頭,若是true則會在響應頭添加;須要配合cacheSeconds使用;

 

private int cacheSeconds = -1:緩存過時時間,正數表示須要緩存,負數表示不作任何事情(也就是說保留上次的緩存設置),

      一、cacheSeconds =0時,則將設置以下響應頭數據:

        Pragma:no-cache             // HTTP 1.0的不緩存響應頭

        Expires:1L                  // useExpiresHeader=true時,HTTP 1.0

        Cache-Control :no-cache      // useCacheControlHeader=true時,HTTP 1.1

        Cache-Control :no-store       // useCacheControlNoStore=true時,該設置是防止Firefox緩存

 

      二、cacheSeconds>0時,則將設置以下響應頭數據:

        Expires:System.currentTimeMillis() + cacheSeconds * 1000L    // useExpiresHeader=true時,HTTP 1.0

        Cache-Control :max-age=cacheSeconds                    // useCacheControlHeader=true時,HTTP 1.1

 

      三、cacheSeconds<0時,則什麼都不設置,即保留上次的緩存設置。

 

 

此處簡單說一下以上響應頭的做用,緩存控制已超出本書內容:

HTTP1.0緩存控制響應頭

  Pragma:no-cache:表示防止客戶端緩存,須要強制從服務器獲取最新的數據;

  Expires:HTTP1.0響應頭,本地副本緩存過時時間,若是客戶端發現緩存文件沒有過時則不發送請求,HTTP的日期時間必須是格林威治時間(GMT), 如「Expires:Wed, 14 Mar 2012 09:38:32 GMT」;

 

HTTP1.1緩存控制響應頭

  Cache-Control :no-cache       強制客戶端每次請求獲取服務器的最新版本,不通過本地緩存的副本驗證;

  Cache-Control :no-store       強制客戶端不保存請求的副本,該設置是防止Firefox緩存

  Cache-Control:max-age=[秒]    客戶端副本緩存的最長時間,相似於HTTP1.0的Expires,只是此處是基於請求的相對時間間隔來計算,而非絕對時間。

 

 

還有相關緩存控制機制如Last-Modified(最後修改時間驗證,客戶端的上一次請求時間 在 服務器的最後修改時間 以後,說明服務器數據沒有發生變化 返回304狀態碼)、ETag(沒有變化時不從新下載數據,返回304)。

 

該抽象類默認被AbstractController和WebContentInterceptor繼承。

4.四、AbstractController

該抽象類實現了Controller,並繼承了WebContentGenerator(具備該類的特性,具體請看4.3),該類有以下屬性:

 

boolean synchronizeOnSession = false:表示該控制器是否在執行時同步session,從而保證該會話的用戶串行訪問該控制器。

 

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {               //委託給WebContentGenerator進行緩存控制               checkAndPrepare(request, response, this instanceof LastModified);               //當前會話是否應串行化訪問.               if (this.synchronizeOnSession) {                      HttpSession session = request.getSession(false);                      if (session != null) {                             Object mutex = WebUtils.getSessionMutex(session);                             synchronized (mutex) {                                    return handleRequestInternal(request, response);                             }                      }               }               return handleRequestInternal(request, response); }

 

能夠看出AbstractController實現了一些特殊功能,如繼承了WebContentGenerator緩存控制功能,並提供了可選的會話的串行化訪問功能。並且提供了handleRequestInternal方法,所以咱們應該在具體的控制器類中實現handleRequestInternal方法,而再也不是handleRequest。

 

 

AbstractController使用方法:

首先讓咱們使用AbstractController來重寫第二章的HelloWorldController:

 

public class HelloWorldController extends AbstractController { 	@Override 	protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception { 		//一、收集參數 		//二、綁定參數到命令對象 		//三、調用業務對象 		//四、選擇下一個頁面 		ModelAndView mv = new ModelAndView(); 		//添加模型數據 能夠是任意的POJO對象 		mv.addObject("message", "Hello World!"); 		//設置邏輯視圖名,視圖解析器會根據該名字解析到具體的視圖頁面 		mv.setViewName("hello"); 		return mv; 	} } 

 

<!— 在chapter4-servlet.xml配置處理器 --> <bean name="/hello" class="cn.javass.chapter4.web.controller.HelloWorldController"/> 

 

從如上代碼咱們能夠看出:

一、繼承AbstractController

二、實現handleRequestInternal方法便可。

 

直接經過response寫響應

若是咱們想直接在控制器經過response寫出響應呢,如下代碼幫咱們闡述:

 

public class HelloWorldWithoutReturnModelAndViewController extends AbstractController { 	@Override 	protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception {  		resp.getWriter().write("Hello World!!");		 		//若是想直接在該處理器/控制器寫響應 能夠經過返回null告訴DispatcherServlet本身已經寫出響應了,不須要它進行視圖解析 		return null; 	} } 

 

 

<!— 在chapter4-servlet.xml配置處理器 --> <bean name="/helloWithoutReturnModelAndView" class="cn.javass.chapter4.web.controller.HelloWorldWithoutReturnModelAndViewController"/> 

 

從如上代碼能夠看出若是想直接在控制器寫出響應,只須要經過response寫出,並返回null便可。

 

強制請求方法類型:

 

<!— 在chapter4-servlet.xml配置處理器 --> <bean name="/helloWithPOST" class="cn.javass.chapter4.web.controller.HelloWorldController">         <property name="supportedMethods" value="POST"></property> </bean>

 

 以上配置表示只支持POST請求,若是是GET請求客戶端將收到「HTTP Status 405 - Request method 'GET' not supported」。

 

好比註冊/登陸可能只容許POST請求。

 

當前請求的session前置條件檢查,若是當前請求無session將拋出HttpSessionRequiredException異常:

 

<!— 在chapter4-servlet.xml配置處理器 --> <bean name="/helloRequireSession" class="cn.javass.chapter4.web.controller.HelloWorldController">         <property name="requireSession" value="true"/> </bean>

 

在進入該控制器時,必定要有session存在,不然拋出HttpSessionRequiredException異常。

 

Session同步:

即同一會話只能串行訪問該控制器。

 

客戶端端緩存控制:

一、緩存5秒,cacheSeconds=5

 

package cn.javass.chapter4.web.controller; //省略import public class HelloWorldCacheController extends AbstractController { 	@Override 	protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception { 		 		//點擊後再次請求當前頁面 		resp.getWriter().write("<a href=''>this</a>"); 		return null; 	} } 

 

<!— 在chapter4-servlet.xml配置處理器 --> <bean name="/helloCache"  class="cn.javass.chapter4.web.controller.HelloWorldCacheController"> <property name="cacheSeconds" value="5"/> </bean> 

 

如上配置表示告訴瀏覽器緩存5秒鐘:

 

開啓chrome瀏覽器調試工具:

 

服務器返回的響應頭以下所示:

 

添加了「Expires:Wed, 14 Mar 2012 09:38:32 GMT」 和「Cache-Control:max-age=5」 表示容許客戶端緩存5秒,當你點「this」連接時,會發現以下:

 

並且服務器也沒有收到請求,當過了5秒後,你再點「this」連接會發現又從新請求服務器下載新數據。

 

注:下面提到一些關於緩存控制的一些特殊狀況:

    一、對於通常的頁面跳轉(如超連接點擊跳轉、經過js調用window.open打開新頁面都是會使用瀏覽器緩存的,在未過時狀況下會直接使用瀏覽器緩存的副本,在未過時狀況下一次請求也不發送);

    二、對於刷新頁面(如按F5鍵刷新),會再次發送一次請求到服務器的;

 

二、不緩存,cacheSeconds=0

 

<!— 在chapter4-servlet.xml配置處理器 --> <bean name="/helloNoCache" class="cn.javass.chapter4.web.controller.HelloWorldCacheController"> <property name="cacheSeconds" value="0"/> </bean>

 

以上配置會要求瀏覽器每次都去請求服務器下載最新的數據:

 

 

三、cacheSeconds<0,將不添加任何數據

響應頭什麼緩存控制信息也不加。

 

四、Last-Modified緩存機制

(一、在客戶端第一次輸入url時,服務器端會返回內容和狀態碼200表示請求成功並返回了內容;同時會添加一個「Last-Modified」的響應頭表示此文件在服務器上的最後更新時間,如「Last-Modified:Wed, 14 Mar 2012 10:22:42 GMT」表示最後更新時間爲(2012-03-14 10:22);

(二、客戶端第二次請求此URL時,客戶端會向服務器發送請求頭 「If-Modified-Since」,詢問服務器該時間以後當前請求內容是否有被修改過,如「If-Modified-Since: Wed, 14 Mar 2012 10:22:42 GMT」,若是服務器端的內容沒有變化,則自動返回 HTTP 304狀態碼(只要響應頭,內容爲空,這樣就節省了網絡帶寬)。

 

客戶端強制緩存過時:

(一、能夠按ctrl+F5強制刷新(會添加請求頭 HTTP1.0 Pragma:no-cache和 HTTP1.1 Cache-Control:no-cache、If-Modified-Since請求頭被刪除)表示強制獲取服務器內容,不緩存。

(二、在請求的url後邊加上時間戳來從新獲取內容,加上時間戳後瀏覽器就認爲不是同一分內容:

http://sishuok.com/?2343243243 和 http://sishuok.com/?34334344 是兩次不一樣的請求。

 

Spring也提供了Last-Modified機制的支持,只須要實現LastModified接口,以下所示:

 

package cn.javass.chapter4.web.controller; public class HelloWorldLastModifiedCacheController extends AbstractController implements LastModified { 	private long lastModified; 	protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws Exception { 		//點擊後再次請求當前頁面 		resp.getWriter().write("<a href=''>this</a>"); 		return null; 	} 	public long getLastModified(HttpServletRequest request) { 		if(lastModified == 0L) { 			//TODO 此處更新的條件:若是內容有更新,應該從新返回內容最新修改的時間戳 			lastModified = System.currentTimeMillis(); 		} 		return lastModified; 	}	 } 

 

<!— 在chapter4-servlet.xml配置處理器 -->    <bean name="/helloLastModified"  class="cn.javass.chapter4.web.controller.HelloWorldLastModifiedCacheController"/> 

 

HelloWorldLastModifiedCacheController只須要實現LastModified接口的getLastModified方法,保證當內容發生改變時返回最新的修改時間便可。

 

分析:

(一、發送請求到服務器,如(http://localhost:9080/springmvc-chapter4/helloLastModified),則服務器返回的響應爲:



(二、再次按F5刷新客戶端,返回狀態碼304表示服務器沒有更新過:

 

(三、重啓服務器,再次刷新,會看到200狀態碼(由於服務器的lastModified時間變了)。

 

Spring判斷是否過時,經過以下代碼,即請求的「If-Modified-Since」 大於等於當前的getLastModified方法的時間戳,則認爲沒有修改:

this.notModified = (ifModifiedSince >= (lastModifiedTimestamp / 1000 * 1000));

 

五、ETag(實體標記)緩存機制

(1:瀏覽器第一次請求,服務器在響應時給請求URL標記,並在HTTP響應頭中將其傳送到客戶端,相似服務器端返回的格式:「ETag:"0f8b0c86fe2c0c7a67791e53d660208e3"」

(2:瀏覽器第二次請求,客戶端的查詢更新格式是這樣的:「If-None-Match:"0f8b0c86fe2c0c7a67791e53d660208e3"」,若是ETag沒改變,表示內容沒有發生改變,則返回狀態304。

 

 

Spring也提供了對ETag的支持,具體須要在web.xml中配置以下代碼:

 

<filter>    <filter-name>etagFilter</filter-name>    <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class> </filter> <filter-mapping>    <filter-name>etagFilter</filter-name>    <servlet-name>chapter4</servlet-name> </filter-mapping>

 

此過濾器只過濾到咱們DispatcherServlet的請求。

 

分析:

1):發送請求到服務器:「http://localhost:9080/springmvc-chapter4/hello」,服務器返回的響應頭中添加了(ETag:"0f8b0c86fe2c0c7a67791e53d660208e3"):

 

2):瀏覽器再次發送請求到服務器(按F5刷新),請求頭中添加了「If-None-Match:

"0f8b0c86fe2c0c7a67791e53d660208e3"」,響應返回304代碼,表示服務器沒有修改,而且響應頭再次添加了「ETag:"0f8b0c86fe2c0c7a67791e53d660208e3"」(每次都須要計算):

 

那服務器端是如何計算ETag的呢?

 

protected String generateETagHeaderValue(byte[] bytes) {               StringBuilder builder = new StringBuilder("\"0");               DigestUtils.appendMd5DigestAsHex(bytes, builder);               builder.append('"');               return builder.toString(); }

 

bytes是response要寫回到客戶端的響應體(即響應的內容數據),是經過MD5算法計算的內容的摘要信息。也就是說若是服務器內容不發生改變,則ETag每次都是同樣的,即服務器端的內容沒有發生改變。

相關文章
相關標籤/搜索