Terry.Li-彬

          虛其心,可解天下之問;專其心,可治天下之學(xué);靜其心,可悟天下之理;恒其心,可成天下之業(yè)。

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            143 隨筆 :: 344 文章 :: 130 評論 :: 0 Trackbacks

          導(dǎo)言

          REST方式的應(yīng)用程序構(gòu)架在近日所產(chǎn)生的巨大影響突出了Web應(yīng)用程序的優(yōu)雅設(shè)計(jì)的重要性。現(xiàn)在人們開始理解“WWW架構(gòu)”內(nèi)在的可測量性及彈 性,并且已經(jīng)開始探索使用其范例的更好的方式。在本文中,我們將討論一個(gè)Web應(yīng)用開發(fā)工具——“簡陋的、卑下的”ETags,以及如何在基于 SpringFramework的動(dòng)態(tài)Web應(yīng)用程序中集成這個(gè)工具,來提高應(yīng)用的性能及可測性。

          我們將要使用的基于Spring的應(yīng)用程序是基于“petclinic”(寵物門診?)的一個(gè)應(yīng)用。在您下載的程序包中,包含了如何加入必要的配置和源代碼讓你親自體驗(yàn)該程序的介紹。

          什么是ETag
          ?
          在HTTP協(xié)議規(guī)范中,ETag被定義為“被請求的變量的實(shí)體值”。(
          參見 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - Section 14.19。)換句話說,ETag是一個(gè)與Web資源相關(guān)聯(lián)的標(biāo)記。典型的Web資源是一個(gè)Web頁面,但也可以是一個(gè)JSON格式或者XML格式的文檔。服務(wù)器可以指出一個(gè)標(biāo)記是什么及其意義,并將這個(gè)標(biāo)記放在HTTP頭重傳送給客戶端。

          ETag如何提高應(yīng)用程序性能

          ETag和一個(gè)GET請求的“If-None-Match”頭信息一起使用,服務(wù)器開發(fā)者以此來使用客戶端緩存的優(yōu)勢。服務(wù)器在客戶端的一次請求時(shí) 產(chǎn)生ETag,并在以后的請求中判斷被請求資源是否發(fā)生了變化。確切的說,客戶端將這個(gè)標(biāo)記傳回給服務(wù)器,來驗(yàn)證它自己的緩存是否有效。

          整個(gè)處理過程如下:

          客戶端請求頁面A
          服務(wù)器響應(yīng),返回頁面A,附加ETag
          客戶端顯示A,并將頁面和ETag一并緩存
          客戶端再次請求頁面A,請求中包含了上次請求頁面A時(shí)返回的ETag
          服務(wù)器檢查客戶端發(fā)送過來的ETag,并確定頁面A在該客戶端上次請求后到現(xiàn)在沒有發(fā)生過變化,因此,發(fā)送一個(gè)304(未改變)響應(yīng)頭給客戶端,附帶一個(gè)空的響應(yīng)體。

          文章的剩余部分將討論在基于SpringFramework的使用SpringMVC的Web應(yīng)用程序中使用ETag兩種方式。首先,我們將通過一 個(gè)Servlet2.3 過濾器,使用由計(jì)算請求返回結(jié)果的MD5值而產(chǎn)生的ETag(一個(gè)簡單的ETag實(shí)現(xiàn))。第二種方式使用一種更加“專業(yè)”的方式通過跟蹤頁面呈現(xiàn)所用到的 模型的變化來確定ETag的有效性(一個(gè)“專業(yè)”的ETag實(shí)現(xiàn))。雖然我們在這里使用了Spring MVC,但這個(gè)技術(shù)適用于其他任何的MVC框架。

          在繼續(xù)之前,我們有必要明確,ETag技術(shù)是為了希望改進(jìn)動(dòng)態(tài)產(chǎn)生的頁面的訪問速度而提出的。作為一個(gè)完整的性能優(yōu)化方案和性能分析,其他的性能優(yōu)化技術(shù)依然應(yīng)當(dāng)被考慮。

          自頂向下的Web緩存

          本文首先討論將HTTP緩存技術(shù)應(yīng)用于動(dòng)態(tài)頁面。尋求Web應(yīng)用程序優(yōu)化方案時(shí),我們應(yīng)當(dāng)采用一個(gè)完整的,自頂向下的步驟。從根本上說,理解HTTP請求的過程是很重要的,采用哪種具體的技術(shù)取決于你在什么場合。例如:

          Apache可以放在你的Servlet容易之前,來接受如圖片,js請求,同時(shí)也可以使用FileETag指令產(chǎn)生ETag響應(yīng)頭。

          使用Javascript優(yōu)化技術(shù),例如將多個(gè)js文件合并,并去除空格等無用信息。

          利用GZip和Cache-Control響應(yīng)頭。

          使用JamonPerformanceMonitorInterceptor確定你的Spring應(yīng)用系統(tǒng)中的性能瓶頸。

          確定你充分地使用了ORM工具的緩存機(jī)制,從而使得實(shí)體信息不是頻繁的從數(shù)據(jù)庫中重新加載。搞清楚如何讓查詢緩存很好的工作需要一定的時(shí)間。

          確保盡量少聰數(shù)據(jù)庫中重新加載數(shù)據(jù),特別是一些大的列表。大列表應(yīng)當(dāng)被按頁分割,對每一頁的請求返回大列表的一個(gè)小的子集。

          Session中保存盡量少的信息。這降低了內(nèi)存要求,在建立應(yīng)用層集群時(shí)將會(huì)顯得非常有用。

          使用一個(gè)數(shù)據(jù)庫調(diào)試工具,確定查詢時(shí)使用了哪些索引,查詢時(shí)數(shù)據(jù)表將不會(huì)被鎖定。

          當(dāng)然了,性能優(yōu)化的最佳格言是適用的:測量兩次,切割一次。(多次測試后再修改)
          等等,上面的話是對木匠說的,但雖然如此,它一樣適用于我們!


          ?一個(gè)內(nèi)容主體ETag過濾器

          我們將看到的第一種方式是建立一個(gè)Servlet過濾器基于頁面內(nèi)容(MVC中的View)來產(chǎn)生ETag標(biāo)記。乍一看,使用這種方式對性能的提升 似乎沒什么大的作用。服務(wù)器依然需要聲稱頁面,并且增加了計(jì)算標(biāo)記值的時(shí)間。但是,在這里我們的目的是減少帶寬占用。這對于很多的反應(yīng)時(shí)間很長的情形是一 個(gè)很大的益處,例如如果你的應(yīng)用的服務(wù)器和客戶端分別在地球的不同半球上。我曾看到一個(gè)從東京發(fā)出的對紐約的某臺(tái)服務(wù)器的請求,響應(yīng)長達(dá)350毫秒。考慮 并發(fā)用戶因素后,這將成為一個(gè)重大的瓶頸。


          代碼

          我們用于產(chǎn)生標(biāo)記的技術(shù)是計(jì)算頁面返回內(nèi)容的MD5值。創(chuàng)建一個(gè)響應(yīng)包裝器將完成這個(gè)工作。包裝器使用一個(gè)字節(jié)數(shù)組來保存返回內(nèi)容,在過濾器鏈處理完成之后,我們計(jì)算這個(gè)字節(jié)數(shù)組的MD5哈希值。

          doFilter方法的實(shí)現(xiàn)如下:


          ?1 public ? void ?doFilter(ServletRequest?req,?ServletResponse?res,?FilterChain?chain)? throws ?IOException,
          ?2 ?ServletException? {
          ?3 ??HttpServletRequest?servletRequest? = ?(HttpServletRequest)?req;
          ?4 ??HttpServletResponse?servletResponse? = ?(HttpServletResponse)?res;
          ?5
          ?6 ???ByteArrayOutputStream?baos? = ? new ?ByteArrayOutputStream();
          ?7 ??ETagResponseWrapper?wrappedResponse? = ? new ?ETagResponseWrapper(servletResponse,?baos);
          ?8 ??chain.doFilter(servletRequest,?wrappedResponse);
          ?9
          10 ??? byte []?bytes? = ?baos.toByteArray();
          11
          12 ???String?token? = ? ' " ' ? + ?ETagComputeUtils.getMd5Digest(bytes)? + ? ' " ' ;
          13 ??servletResponse.setHeader( " ETag " ,?token);? // ?always?store?the?ETag?in?the?header
          14
          15 ???String?previousToken? = ?servletRequest.getHeader( " If-None-Match " );
          16 ?? if ?(previousToken? != ? null ? && ?previousToken.equals(token))? {? // ?compare?previous?token?with?current?one
          17 ???logger.debug( " ETag?match:?returning?304?Not?Modified " );
          18 ???servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);
          19 ??? // ?use?the?same?date?we?sent?when?we?created?the?ETag?the?first?time?through
          20 ???servletResponse.setHeader( " Last-Modified " ,?servletRequest.getHeader( " If-Modified-Since " ));
          21 ??}
          ? else ?? {??? // ?first?time?through?-?set?last?modified?time?to?now?
          22 ???Calendar?cal? = ?Calendar.getInstance();
          23 ???cal.set(Calendar.MILLISECOND,? 0 );
          24 ???Date?lastModified? = ?cal.getTime();
          25 ???servletResponse.setDateHeader( " Last-Modified " ,?lastModified.getTime());
          26
          27 ????logger.debug( " Writing?body?content " );
          28 ???servletResponse.setContentLength(bytes.length);
          29 ???ServletOutputStream?sos? = ?servletResponse.getOutputStream();
          30 ???sos.write(bytes);
          31 ???sos.flush();
          32 ???sos.close();
          33 ??}

          34 ?}
          Listing 1: ETagContentFilter.doFilter

          應(yīng)該注意到,我們設(shè)置了“Last-Modified”響應(yīng)頭。這是因?yàn)槲覀冃枰M織良好的內(nèi)容格式,以對應(yīng)哪些無法理解ETag響應(yīng)頭的客戶端。
          上面的示例代碼用到了一個(gè)EtagComputeUtils工具類來產(chǎn)生一個(gè)對象的字節(jié)數(shù)組表示并處理MD5雜湊邏輯。在這里我使用javax.security.MessageDigest來計(jì)算MD5值。

          ?1public?static?byte[]?serialize(Object?obj)?throws?IOException?{
          ?2??byte[]?byteArray?=?null;
          ?3??ByteArrayOutputStream?baos?=?null;
          ?4??ObjectOutputStream?out?=?null;
          ?5??try?{
          ?6???//?These?objects?are?closed?in?the?finally.
          ?7???baos?=?new?ByteArrayOutputStream();
          ?8???out?=?new?ObjectOutputStream(baos);
          ?9???out.writeObject(obj);
          10???byteArray?=?baos.toByteArray();
          11??}
          ?finally?{
          12???if?(out?!=?null)?{
          13????out.close();
          14???}

          15??}

          16??return?byteArray;
          17?}

          18
          19??public?static?String?getMd5Digest(byte[]?bytes)?{
          20??MessageDigest?md;
          21??try?{
          22???md?=?MessageDigest.getInstance("MD5");
          23??}
          ?catch?(NoSuchAlgorithmException?e)?{
          24???throw?new?RuntimeException("MD5?cryptographic?algorithm?is?not?available.",?e);
          25??}

          26??byte[]?messageDigest?=?md.digest(bytes);
          27??BigInteger?number?=?new?BigInteger(1,?messageDigest);
          28??//?prepend?a?zero?to?get?a?"proper"?MD5?hash?value
          29??StringBuffer?sb?=?new?StringBuffer('0');
          30??sb.append(number.toString(16));
          31??return?sb.toString();
          32?}
          ?
          33Listing?2:?ETagComputeUtils?

          在Web.xml中調(diào)用這個(gè)過濾器是很簡單的:
          1<filter>
          2????<filter-name>ETag?Content?Filter</filter-name>
          3????<filter-class>org.springframework.samples.petclinic.web.ETagContentFilter</filter-class>
          4</filter>
          5
          6<filter-mapping>
          7????<filter-name>ETag?Content?Filter</filter-name>
          8????<url-pattern>/*.htm</url-pattern>
          9</filter-mapping>
          Listing 3: Configuration of the filter in web.xml.


          每一個(gè)htm文件將被EtagContentFilter過濾,如果該文件在上次請求后沒有發(fā)生變化,則返回一個(gè)空的HTTP響應(yīng)體。

          上面討論的方式對于確定類型的頁面很有用,但也有一些缺點(diǎn)。
          頁面在服務(wù)器段生成之后,在返回給客戶端之前,我們計(jì)算了ETag值,如果ETag匹配,那么我們實(shí)在是沒有必要去取出模型數(shù)據(jù),因?yàn)殇秩境鰜淼捻撁鎸⒉粫?huì)返回給客戶端。
          對于在頁腳呈現(xiàn)日期和時(shí)間的頁面,每次請求都是不同的,即使頁面的主題內(nèi)容并沒有發(fā)生改變。
          下面,我們將看到另一種可選的方法——通過理解構(gòu)建頁面的底層數(shù)據(jù)來解決上面的限制帶來的問題。

          ETag攔截器
          Spring MVC中的HTTP請求傳遞途徑包含了一種可以在控制器處理請求之前插入一個(gè)攔截器的能力。這對于插入ETag對比邏輯來說是一個(gè)極其合適的切入點(diǎn),在這里,如果發(fā)現(xiàn)構(gòu)建頁面的數(shù)據(jù)沒有發(fā)生變化,我們就可以停止更進(jìn)一步的處理。
          這 里的訣竅是如何知道構(gòu)建所請求的頁面的數(shù)據(jù)沒有發(fā)生變化。為了本文的目的,我創(chuàng)建了一個(gè)簡單的ModifiedObjectTracker,通過 Hiberante事件監(jiān)聽器來跟蹤新增、更新、刪除操作。跟蹤器將為每一個(gè)頁面保持一個(gè)為一個(gè)數(shù)字,以及一個(gè)影響到該頁面的持久化實(shí)體的Map。如果一 個(gè)POJO發(fā)生了變化,那么一個(gè)技術(shù)其將增加所有用到了這個(gè)POJO的頁面對應(yīng)的數(shù)字。將這個(gè)數(shù)字作為ETag,當(dāng)客戶端將ETag返回時(shí),我們將會(huì)知道 一個(gè)頁面所用到的模型是否發(fā)生了變化。

          代碼


          從ModifiedObjectTracker開始:
          1?public?interface?ModifiedObjectTracker?{
          2?????void?notifyModified(>?String?entity);
          3?}?

          很簡單吧?它的實(shí)現(xiàn)會(huì)比較有意思。每當(dāng)一個(gè)實(shí)體發(fā)生了變化,我們?yōu)槊恳粋€(gè)用到了該實(shí)體的頁面更新對應(yīng)的計(jì)數(shù)器。
          ?1?public?void?notifyModified(String?entity)?{
          ?2???//?entityViewMap?is?a?map?of?entity?->?list?of?view?names
          ?3???List?views?=?getEntityViewMap().get(entity);
          ?4?
          ?5????if?(views?==?null)?{
          ?6????return;?//?no?views?are?configured?for?this?entity
          ?7???}
          ?8?
          ?9????synchronized?(counts)?{
          10????for?(String?view?:?views)?{
          11?????Integer?count?=?counts.get(view);
          12?????counts.put(view,?++count);
          13????}
          14???}
          15??}?

          一次“變化”就是一次新增、修改或者刪除操作。下面是針對刪除操作的處理器列表(作為事件監(jiān)聽器配置在Hibernate 3 LocalSessionFactoryBean中)。
          public?class?DeleteHandler?extends?DefaultDeleteEventListener?{
          ??
          private?ModifiedObjectTracker?tracker;

          ???
          public?void?onDelete(DeleteEvent?event)?throws?HibernateException?{
          ???getModifiedObjectTracker().notifyModified(event.getEntityName());
          ??}

          ??
          public?ModifiedObjectTracker?getModifiedObjectTracker()?{
          ???
          return?tracker;
          ??}
          ???
          public?void?setModifiedObjectTracker(ModifiedObjectTracker?tracker)?{
          ???
          this.tracker?=?tracker;
          ??}
          ?}?

          ModifiedObjectTracker將通過Spring配置注射到DeleteHandler中。同時(shí),將會(huì)有一個(gè)SaveOrUpdateHandler處理實(shí)體的新增和修改。
          如果客戶端發(fā)回了一個(gè)當(dāng)前有效的ETag(意思是內(nèi)容在上次請求后未曾發(fā)生改變),我們將阻止更多的處理邏輯,以實(shí)現(xiàn)我們的性能提升。在Spring MVC中,可以使用一個(gè)HandlerInterceptorAdaptor ,并重寫preHandle方法:

          public?final?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)?throws
          ?ServletException,?IOException?{
          ??String?method?
          =?request.getMethod();
          ??
          if?(!"GET".equals(method))
          ???
          return?true;

          ???String?previousToken?
          =?request.getHeader("If-None-Match");
          ??String?token?
          =?getTokenFactory().getToken(request);

          ???
          //?compare?previous?token?with?current?one
          ??if?((token?!=?null)?&&?(previousToken?!=?null?&&?previousToken.equals('"'?+?token?+?'"')))?{
          ???response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
          ???
          //?re-use?original?last?modified?timestamp
          ???response.setHeader("Last-Modified",?request.getHeader("If-Modified-Since"))
          ???
          return?false;?//?no?further?processing?required
          ??}

          ???
          //?set?header?for?the?next?time?the?client?calls
          ??if?(token?!=?null)?{?
          ???response.setHeader(
          "ETag",?'"'?+?token?+?'"');

          ????
          //?first?time?through?-?set?last?modified?time?to?now
          ???Calendar?cal?=?Calendar.getInstance();
          ???cal.set(Calendar.MILLISECOND,?
          0);
          ???Date?lastModified?
          =?cal.getTime();
          ???response.setDateHeader(
          "Last-Modified",?lastModified.getTime());
          ??}

          ???
          return?true;
          ?}?


          首先我們需要確定我們處理的是一個(gè)GET請求(ETag可以在客戶端發(fā)出PUT請求時(shí)驗(yàn)證更新是否沖突,但那已經(jīng)超出了本文的范圍)。如果 標(biāo)記和服務(wù)器上次返回的標(biāo)記相匹配,則返回一個(gè)304位發(fā)生改變響應(yīng),并繞過后面的處理鏈。否則,我們設(shè)置一個(gè)ETag響應(yīng)頭,以備客戶端下次請求同樣的 頁面。

          可以看到,我將產(chǎn)生標(biāo)記的邏輯抽象出來形成了一個(gè)接口,如此我們則可以使用不同的標(biāo)記生成策略。該接口只有一個(gè)方法:

          public?interface?ETagTokenFactory?{
          ??String?getToken(HttpServletRequest?request);
          }?

          為了少列出一些代碼,我的SampleTokenFactory實(shí)現(xiàn)同時(shí)承擔(dān)了ETagTokenFactory的任務(wù)。如此,我們簡單的將被請求的URL的修改次數(shù)作為標(biāo)記返回。
          public?String?getToken(HttpServletRequest?request)?{
          ??String?view?
          =?request.getRequestURI();
          ??Integer?count?
          =?counts.get(view);
          ??
          if?(count?==?null)?{
          ???
          return?null;
          ??}

          ???
          return?count.toString();
          ?}?


          就這樣!

          討論

          在這里,我們的攔截器將在沒有相關(guān)數(shù)據(jù)發(fā)生變化時(shí)阻止一切收集數(shù)據(jù)和渲染頁面的處理過程。現(xiàn)在,讓我們來看一下HTTP頭,以及在表象之下到底發(fā)生了些什么。示例程序中包含了使得owner.htm使用ETag的配置介紹。
          第一次請求說明用戶已經(jīng)看到了該頁面:

          http://localhost:8080/petclinic/owner.htm?ownerId=10?

          ?GET /petclinic/owner.htm?ownerId=10 HTTP/1.1
          ?Host: localhost:8080
          ?User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
          ?Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          ?Accept-Language: en-us,en;q=0.5
          ?Accept-Encoding: gzip,deflate
          ?Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          ?Keep-Alive: 300
          ?Connection: keep-alive
          ?Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
          ?X-lori-time-1: 1182364348062
          ?If-Modified-Since: Wed, 20 Jun 2007 18:29:03 GMT
          ?If-None-Match: "-1"

          ?HTTP/1.x 304 Not Modified
          ?Server: Apache-Coyote/1.1
          ?Date: Wed, 20 Jun 2007 18:32:30 GMT

          下面我們觸發(fā)一些變化,并觀察ETag是否改變。為這個(gè)Owner增加了一個(gè)Pet:


          ----------------------------------------------------------
          ?http://localhost:8080/petclinic/addPet.htm?ownerId=10

          ?GET /petclinic/addPet.htm?ownerId=10 HTTP/1.1
          ?Host: localhost:8080
          ?User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
          ?Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          ?Accept-Language: en-us,en;q=0.5
          ?Accept-Encoding: gzip,deflate
          ?Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          ?Keep-Alive: 300
          ?Connection: keep-alive
          ?Referer: http://localhost:8080/petclinic/owner.htm?ownerId=10
          ?Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
          ?X-lori-time-1: 1182364356265

          ?HTTP/1.x 200 OK
          ?Server: Apache-Coyote/1.1
          ?Pragma: No-cache
          ?Expires: Thu, 01 Jan 1970 00:00:00 GMT
          ?Cache-Control: no-cache, no-store
          ?Content-Type: text/html;charset=ISO-8859-1
          ?Content-Language: en-US
          ?Content-Length: 2174
          ?Date: Wed, 20 Jun 2007 18:32:57 GMT
          ?----------------------------------------------------------
          ?http://localhost:8080/petclinic/addPet.htm?ownerId=10?
          ?
          ?POST /petclinic/addPet.htm?ownerId=10 HTTP/1.1
          ?Host: localhost:8080
          ?User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
          ?Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          ?Accept-Language: en-us,en;q=0.5
          ?Accept-Encoding: gzip,deflate
          ?Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          ?Keep-Alive: 300
          ?Connection: keep-alive
          ?Referer: http://localhost:8080/petclinic/addPet.htm?ownerId=10
          ?Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
          ?X-lori-time-1: 1182364402968
          ?Content-Type: application/x-www-form-urlencoded
          ?Content-Length: 40
          ?name=Noddy&birthDate=1000-11-11&typeId=5
          ?HTTP/1.x 302 Moved Temporarily
          ?Server: Apache-Coyote/1.1
          ?Pragma: No-cache
          ?Expires: Thu, 01 Jan 1970 00:00:00 GMT
          ?Cache-Control: no-cache, no-store
          ?Location: http://localhost:8080/petclinic/owner.htm?ownerId=10
          ?Content-Language: en-US
          ?Content-Length: 0
          ?Date: Wed, 20 Jun 2007 18:33:23 GMT

          因?yàn)槲覀儧]有為addPet.htm配置ETag,所以不設(shè)置相關(guān)的響應(yīng)頭。現(xiàn)在,我們再次訪問Owener 10,注意相應(yīng)中的ETag成為了1:


          ----------------------------------------------------------
          ?http://localhost:8080/petclinic/owner.htm?ownerId=10

          ?GET /petclinic/owner.htm?ownerId=10 HTTP/1.1
          ?Host: localhost:8080
          ?User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
          ?Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          ?Accept-Language: en-us,en;q=0.5
          ?Accept-Encoding: gzip,deflate
          ?Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          ?Keep-Alive: 300
          ?Connection: keep-alive
          ?Referer: http://localhost:8080/petclinic/addPet.htm?ownerId=10
          ?Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
          ?X-lori-time-1: 1182364403109
          ?If-Modified-Since: Wed, 20 Jun 2007 18:29:03 GMT
          ?If-None-Match: "-1"

          ?HTTP/1.x 200 OK
          ?Server: Apache-Coyote/1.1
          ?Etag: "1"
          ?Last-Modified: Wed, 20 Jun 2007 18:33:36 GMT
          ?Content-Type: text/html;charset=ISO-8859-1
          ?Content-Language: en-US
          ?Content-Length: 4317
          ?Date: Wed, 20 Jun 2007 18:33:45 GMT

          最后,我們再次請求Owener 10,這次ETag起了作用,我們接受到了一個(gè)304未改變信息。

          ----------------------------------------------------------
          ?http://localhost:8080/petclinic/owner.htm?ownerId=10

          ?GET /petclinic/owner.htm?ownerId=10 HTTP/1.1
          ?Host: localhost:8080
          ?User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
          ?Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
          ?Accept-Language: en-us,en;q=0.5
          ?Accept-Encoding: gzip,deflate
          ?Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
          ?Keep-Alive: 300
          ?Connection: keep-alive
          ?Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
          ?X-lori-time-1: 1182364493500
          ?If-Modified-Since: Wed, 20 Jun 2007 18:33:36 GMT
          ?If-None-Match: "1"

          ?HTTP/1.x 304 Not Modified
          ?Server: Apache-Coyote/1.1
          ?Date: Wed, 20 Jun 2007 18:34:55 GMT

          如此,我們使用HTTP緩存降低了帶寬占用,縮短了處理周期。
          The Fine Print: 事實(shí)上,采用更細(xì)粒度的對象變化跟蹤,例如使用對象標(biāo)識。可以更大程度的提高效率。但是,頁面和實(shí)體之間的關(guān)聯(lián)很大程度上是由系統(tǒng)中的數(shù)據(jù)模型設(shè)計(jì)決定 的。上面的實(shí)現(xiàn)(ModifiedObjectTracker)是一個(gè)說明性的例子,謎底是為更深入的嘗試提供思路。上面的實(shí)現(xiàn)的目的不是應(yīng)用于實(shí)際的生 產(chǎn)環(huán)境中(例如不適用于集群環(huán)境),一種更遠(yuǎn)的考慮是使用數(shù)據(jù)庫的觸發(fā)器跟蹤數(shù)據(jù)變化,讓攔截器監(jiān)測觸發(fā)器輸出結(jié)果所在的數(shù)據(jù)表。

          結(jié)論

          我們已經(jīng)看到了使用ETag降低貸款占用和縮短處理周期的兩種方法。我所希望的是這篇文章為你現(xiàn)在和將來的Web應(yīng)用項(xiàng)目提供了一種思路,以及對底層的ETag響應(yīng)頭的正確理解和使用。
          正 如牛頓所說,“如果我看得更遠(yuǎn),那是因?yàn)槲艺驹诰奕说募绨蛏稀薄W鳛镽EST的核心,這種風(fēng)格的應(yīng)用程序講的是簡單、優(yōu)雅的軟件設(shè)計(jì),不重復(fù)發(fā)明輪子。我 相信了解和使用REST風(fēng)格的架構(gòu)的核心是主流應(yīng)用程序開發(fā)的一個(gè)好的發(fā)展,并且我盼望著在以后的開發(fā)中能夠抬起它的未來。

          posted on 2010-09-29 10:38 禮物 閱讀(931) 評論(1)  編輯  收藏

          評論

          # re: 在基于Spring及Hibernate應(yīng)用程序中使用ETags降低帶寬占用和服務(wù)器壓力 2014-04-13 00:21 最代碼
          我整理到了最代碼網(wǎng)站上了,地址:http://www.zuidaima.com/share/1777391667989504.htm 有問題請回復(fù)  回復(fù)  更多評論
            


          只有注冊用戶登錄后才能發(fā)表評論。

          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 莒南县| 文安县| 扶绥县| 泽普县| 永宁县| 庆元县| 剑川县| 察隅县| 鲜城| 婺源县| 株洲县| 游戏| 库车县| 阜康市| 昔阳县| 东丰县| 体育| 高台县| 凤凰县| 通河县| 根河市| 开阳县| 安阳市| 松桃| 南平市| 大同县| 平塘县| 包头市| 大悟县| 华阴市| 明光市| 红原县| 庄浪县| 锡林浩特市| 白水县| 灵寿县| 扬中市| 绥宁县| 桐柏县| 平南县| 双牌县|