利用GWT開發(fā)高性能Ajax應(yīng)用
對(duì)性能的提升是Ajax受歡迎的主要原因。我們通常以為那些所謂的眩目變換對(duì)于用戶來說是Ajax最吸引人的地方,可能用戶也確實(shí)由于這個(gè)原因而對(duì) Ajax獨(dú)有情鐘。如果你回頭去看那些傳統(tǒng)的web應(yīng)用,會(huì)發(fā)現(xiàn)它們幾乎靜態(tài)到令人反感,所以說用戶僅僅出于這些眩目變換而選擇Ajax不無道理。然而, 如果說眩目的變換真得大大改善了用戶體驗(yàn)的話,那么動(dòng)態(tài)的gif圖片應(yīng)該受到更廣泛的應(yīng)用才是。謝天謝地,Web應(yīng)用早已走過這種幼稚的時(shí)代。Ajax不 會(huì)再重復(fù)動(dòng)態(tài)gif圖片的老路,它不會(huì)再把重點(diǎn)放在這類眩目的變換上了。因此,無論人們是否感受或是意識(shí)到,Ajax真正改善用戶體驗(yàn)的地方還是在對(duì)性能 的提升上。
這篇文章的重點(diǎn)并非要說明Ajax天生在哪些方面比傳統(tǒng)Web應(yīng)用優(yōu)秀。關(guān)于這個(gè)問題,只要將Google地圖與其他Web地圖或者將Gmail與 Hotmail進(jìn)行對(duì)比,自然就可以得出結(jié)論。當(dāng)然,應(yīng)用Ajax的確能顯著改善性能和用戶體驗(yàn)。但在此,我要向大家展示的是如何將Ajax應(yīng)用的性能提 高到一個(gè)新的層次——從而使您的應(yīng)用脫穎而出。
選擇GWT的理由
Google Web Toolkit (GWT)將Ajax的開發(fā)推進(jìn)了一大步,然而面對(duì)當(dāng)下種類繁多的Ajax解決方案,此類新技術(shù)的推廣難免遇到種種挑戰(zhàn)。但無可否認(rèn),在Ajax開發(fā)方 面,GWT給開發(fā)者提供了其他解決方案無可比擬的便利。如果你還沒有受到任何開發(fā)框架束縛的話,實(shí)在沒有什么理由不選擇GWT,因?yàn)镚WT能夠無償?shù)氖箲?yīng) 用的整體性能得到大幅度提升。
我所說的“無償”是指在開發(fā)中可以拋開性能問題不考慮,而將主要精力集中在業(yè)務(wù)邏輯方面,因?yàn)镚WT本身已能使性能得到優(yōu)化。GWT帶有一個(gè)能將 Java代碼編譯成JavaScript的編譯器。如果熟悉編譯語言(C、Java等等)的話,你一定了解平臺(tái)獨(dú)立性是此類語言追求的一個(gè)目標(biāo),因此其編 譯器能夠針對(duì)特定平臺(tái)對(duì)代碼進(jìn)行優(yōu)化,這樣程序員就可以將重點(diǎn)放在代碼的結(jié)構(gòu)組織和可讀性上。GWT編譯器也做了類似的事情,它將Java代碼編譯成一些 高度優(yōu)化的JavaScript文件,每個(gè)文件對(duì)應(yīng)于一種特定的瀏覽器,其中的優(yōu)化步驟還應(yīng)用了普通編譯器中的優(yōu)化方法,去掉了沒有被調(diào)用的函數(shù)和內(nèi)聯(lián)代 碼。此種方式得到的代碼相對(duì)直接編寫的JavaScript代碼要小的多而且做到了瀏覽器獨(dú)立,因此執(zhí)行效率較高。實(shí)質(zhì)上GWT已將JavaScript 看作web中的匯編代碼來處理。在瀏覽器加載JavaScript代碼的時(shí)候,僅僅加載針對(duì)該瀏覽器所需的代碼而已。使用GWT的應(yīng)用比任何直接用 JavaScript實(shí)現(xiàn)的應(yīng)用要來得更精煉更快。對(duì)即將發(fā)布的GWT 1.5版本,GWT開發(fā)團(tuán)隊(duì)堅(jiān)信其編譯器生成的代碼會(huì)比其他任何手工編寫的代碼都要快。以上這些應(yīng)該足以說服大家選用GWT作為Ajax的解決方案,如果 還不夠,還有許多其他充分的理由,比如你可以在開發(fā)GWT程序時(shí)應(yīng)用某些Java開發(fā)工具(能用Eclipse來調(diào)試Ajax程序在我看來確實(shí)是一個(gè)非常 有分量的砝碼)。
錦上添花
還遠(yuǎn)不止這些呢!Ajax已經(jīng)比傳統(tǒng)web應(yīng)用要出色得多,而GWT又遠(yuǎn)超一般的Ajax技術(shù)。只簡(jiǎn)單地做些技術(shù)決定就能讓你將大部分精力放在業(yè)務(wù) 功能上,達(dá)到事半功倍的效果,開發(fā)出完美的應(yīng)用。當(dāng)然,GWT并非憑空就能做到這些,下面我將講述幾種進(jìn)一步提升GWT性能方法。
1. 始終做好緩存
當(dāng)你將GWT的Java代碼編譯成JavaScript后,對(duì)應(yīng)于每個(gè)瀏覽器版本都會(huì)有生成一個(gè)相應(yīng)的文件,該文件采用唯一標(biāo)識(shí)的文件名。這些就是 你的應(yīng)用程序的代碼文件,直接把它們放到一個(gè)web服務(wù)器上就能發(fā)布你的應(yīng)用了。由于文件名是通過對(duì)你的代碼進(jìn)行Hash函數(shù)計(jì)算而得,所以文件名本身就 已包含了版本信息。如果你修改了代碼后重新編譯,生成的文件會(huì)有新的文件名。這意味著要么文件已經(jīng)被下載到了本地瀏覽器,要么從來沒有被請(qǐng)求過,因此就沒 有必要用檢查文件修改日期(HTTP的If-Modified-Since頭)的方法來決定是否需要版本更新。這樣可以減少很多不必要的HTTP請(qǐng)求過 程,雖然這些請(qǐng)求過程單獨(dú)可能很微不足道,但是當(dāng)用戶量達(dá)到一定程度,它們就會(huì)變成不得不考慮的因素。這類請(qǐng)求對(duì)客戶端來說也是一種拖累,因?yàn)閷?duì)同一個(gè)應(yīng) 用,每個(gè)瀏覽器最多只能有兩個(gè)活動(dòng)的請(qǐng)求。很多對(duì)Ajax下載時(shí)間的優(yōu)化都是從減少向服務(wù)器發(fā)送的請(qǐng)求量入手的。
為了避免瀏覽器對(duì)版本的請(qǐng)求,你可以通過配置web服務(wù)器來向客戶端發(fā)送Expires HTTP頭。這個(gè)Expires HTTP頭包含頁面過期的時(shí)間,這樣就可以避免瀏覽器在頁面過期時(shí)間之前發(fā)送版本檢查的請(qǐng)求。在Apache中設(shè)置這些非常容易,只需要將以下內(nèi)容加入 到.htaccess文件即可:
ExpiresDefault "now plus 1 year" |
Apache會(huì)給所有符合*.cache.*模式的文件加上expires頭,設(shè)置其失效日期為一年后,此模式將匹配所有GWT應(yīng)用文件。如果你使 用的是Tomcat,也可直接通過servlet過濾器來添加頭部。增加一個(gè)servlet過濾器非常簡(jiǎn)單,只需要在WEB_INF/web.xml文件 中添加此過濾器的聲明,例如:
CacheFilter |
這樣tomcat就知道在哪里找到此過濾器、知道哪些文件可以通過該過濾器。本例中,/gwt/*模式表示gwt文件夾下的所有文件。這個(gè)過濾器的 實(shí)現(xiàn)類將通過doFilter方法來添加Expires頭。對(duì)GWT應(yīng)用來說,我們需要在每個(gè)不符合*.nocache.*模式的文件里添加此 Expires頭。nocache文件是不需要緩存的,因?yàn)槠渲泻邪姹具x擇的邏輯。以下是這個(gè)過濾器的實(shí)現(xiàn)代碼:
public class CacheFilter implements Filter { private FilterConfig filterConfig; public void doFilter( ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest)request; String requestURI = httpRequest.getRequestURI(); if( !requestURI.contains(".nocache.") ){ long today = new Date().getTime(); HttpServletResponse httpResponse = (HttpServletResponse)response; httpResponse.setDateHeader("Expires", today+31536000000L); } filterChain.doFilter(request, response); } public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } public void destroy() { this.filterConfig = null; } }
2. 程序壓縮
通過去掉未被調(diào)用的方法和艱澀的代碼、使用簡(jiǎn)短的變量名和方法名等方式,GWT編譯器在減少代碼量方面表現(xiàn)得非常出色,但是最后得到的代碼文本仍然 是未經(jīng)壓縮的。因此可以通過gzip壓縮需要部署的應(yīng)用程序的方法進(jìn)一步減小代碼文件的大小。gzip可以將應(yīng)用程序壓縮到原來的70%左右,從而提高應(yīng) 用的下載速度。
幸運(yùn)的是,文件壓縮也可以簡(jiǎn)單地通過配置服務(wù)器來實(shí)現(xiàn),唯一要做的只是在Apache服務(wù)器的.htaccess文件中加上以下語句:
SetOutputFilter DEFLATE |
Apache首先會(huì)自動(dòng)與瀏覽器進(jìn)行溝通,根據(jù)瀏覽器的支持情況從而決定是否發(fā)送壓縮版本,不過目前所有主流瀏覽器都支持gzip壓縮。
如果使用的是Tomcat,那么可以直接利用server.xml文件中Connector元素,只要加上以下的屬性就可以進(jìn)行程序文件的壓縮了:
compression="on" |
#p#
3. 圖片打包
Ajax應(yīng)用借助于瀏覽器和HTTP協(xié)議強(qiáng)大的分布力量,然而瀏覽器和HTTP協(xié)議本身對(duì)分布Ajax應(yīng)用沒有特別的優(yōu)化。Ajax應(yīng)用是需要部署 的,在這一點(diǎn)上它跟桌面應(yīng)用程序有些相象,而傳統(tǒng)的web程序使用的是共享資源分布模型(shared resource distribution model),在程序運(yùn)行過程中瀏覽器和服務(wù)器間會(huì)不斷進(jìn)行交互,從而對(duì)頁面所需要的資源進(jìn)行管理。這種方式使資源能夠在頁面間共享和緩存,從而保證打開 新頁面所需的下載量達(dá)到最小化。在Ajax應(yīng)用中,資源一般不會(huì)分布在頁面間,因此不需要單獨(dú)對(duì)其進(jìn)行下載緩存。不過,對(duì)于Ajax應(yīng)用,在下載應(yīng)用程序 資源時(shí)采用傳統(tǒng)的分布式模型也并非不可行,許多Ajax應(yīng)用也正是這么做的。
然而,你可以選擇將程序中用到的所有圖片合并到一個(gè)文件中,以減少HTTP請(qǐng)求的次數(shù)。這樣可以突破同一時(shí)間只能發(fā)送兩個(gè)請(qǐng)求的限制,一次性地下載所有圖片。
GWT從1.4版本開始支持ImageBundle接口。在這個(gè)接口中可以為每一個(gè)圖片建立一個(gè)方法,編譯器會(huì)將所有的圖片組合到一個(gè)文件中,并將 圖片數(shù)據(jù)的Hash做為新文件的文件名(象程序代碼一樣永久緩存這個(gè)文件)。一次性打包合并的圖片數(shù)量是沒有限制的,所有這些圖片只需要一次請(qǐng)求就可以全 部下載。
在已經(jīng)完成的幾個(gè)GWT項(xiàng)目中我一直沿用將基本圖片打包的做法,以下是示例代碼:
public interface Images extends ImageBundle {
/**
* @gwt.resource membersm.png
*/
AbstractImagePrototype member();
/**
* @gwt.resource away.png
*/
AbstractImagePrototype away();
/**
* @gwt.resource starsm.gif
*/
AbstractImagePrototype star();
/**
* @gwt.resource turn.png
*/
AbstractImagePrototype turn();
/**
* @gwt.resource user_add.png
*/
AbstractImagePrototype addFavorite();
}
需要注意的是每個(gè)方法都有一個(gè)公共注解來指明圖片的文件名,方法的返回類型都是AbstractImagePrototype。 AbstractImagePrototype類的createImage方法將返回一個(gè)可以在程序接口中使用的圖片widget。以下的代碼揭示了如何 使用該圖片包:
Images images = (Images) GWT.create(Images.class); |
這一切看上去很簡(jiǎn)單,不過正是這些看似簡(jiǎn)單的東西開啟了GWT性能提升之門。
4. 使用StyleInjector
我們又該如何處理CSS文件以及CSS圖片等應(yīng)用程序資源呢?在傳統(tǒng)的web分布模型中,這些都作為外部資源而被獨(dú)立下載和緩存。在Ajax應(yīng)用 中,這樣做意味著額外的HTTP請(qǐng)求和緩慢的程序加載。目前,GWT對(duì)此尚未提供任何優(yōu)化,但在GWT的官方孵化項(xiàng)目中有一些很有意思的GWT代碼,這些 代碼很可能會(huì)包含在GWT的未來版本中,其中尤其值得關(guān)注的是ImmutableResourceBundle和StyleInjector兩個(gè)類。
ImmutableResourceBundle的功能和ImageBundle很相似,但是它可以用于包括CSS和CSS圖片在內(nèi)的任何類型的資源。這 個(gè)類的目的在于為程序資源提供一個(gè)抽象,使得處理它們的方式對(duì)瀏覽器來說達(dá)到最優(yōu)化。下面這個(gè)類即是一個(gè)可用于加載CSS文件及其相關(guān)資源的例子:
public interface Resources extends ImmutableResourceBundle {
/**
* @gwt.resource main.css
*/
public TextResource mainCss();
/**
* @gwt.resource back.gif
*/
public DataResource background();
/**
* @gwt.resource titlebar.gif
*/
public DataResource titleBar();
/**
* @gwt.resource dialog-header.png
*/
public DataResource dialogHeader();
}
這個(gè)類會(huì)為每個(gè)資源指定一個(gè)文件和方法,這一點(diǎn)和ImageBundle 非常類似,但它的返回類型是DataResource 或TextResource。對(duì)于TextResource類,我們可以通過其getText 方法得到指定文件中的內(nèi)容,而對(duì)于DataResource類,我們可以用getUrl方法來得到資源的引用(例如對(duì)圖片或者IFRAME的引用)。不同 的瀏覽器對(duì)這些數(shù)據(jù)的加載方式各不相同,但我們無須擔(dān)心這些。大多數(shù)情況下,數(shù)據(jù)會(huì)通過使用URL前綴以內(nèi)聯(lián)URL的方式出現(xiàn)。這個(gè)類的用途很廣泛,但是 最直接的應(yīng)用可能還是將CSS與其他程序文件一塊打包使用。
可以注意到,在這個(gè)接口中引用了一個(gè)CSS文件及其一些圖片。在這種情況下,該接口被拿來將CSS及其圖片與程序文件進(jìn)行打包,從而減少HTTP請(qǐng) 求的次數(shù)和縮短應(yīng)用啟動(dòng)時(shí)間。在CSS文件中一般會(huì)指定一些背景圖片,但會(huì)使用占位符(placeholder)來取代真實(shí)的圖片URL。這些占位符被用 來引用打包的文件中其他一些元素,尤其是圖片。例如,main.css文件有這樣一個(gè)名為gwt-DialogBox的CSS規(guī)則:
.gwt-DialogBox{ background-image:url('%background%') repeat-x; } |
如果要在程序中應(yīng)用此CSS文件和圖片,你需要用到孵化項(xiàng)目中的StyleInjector 類。StyleInjector會(huì)將CSS文件中的占位符匹配到打包文件中的特定資源,然后再將CSS文件注入到瀏覽器中供應(yīng)用程序使用。這聽起是挺復(fù) 雜,但實(shí)際使用還是比較方便的,重點(diǎn)是它能改善應(yīng)用的性能。下面這段代碼是使用StyleInjector將CSS從資源包注入到應(yīng)用程序中的一個(gè)例子:
Resources resources = (Resources)GWT.create(Resources.class); |
需要注意的是以上這些目前還是孵化項(xiàng)目的一部分,在正式發(fā)布前隨時(shí)都有可能做調(diào)整。
結(jié)論
總之,Ajax應(yīng)用相對(duì)于傳統(tǒng)web應(yīng)用在使用性上有質(zhì)的飛躍,同時(shí)GWT所提供的工具能使你的Ajax性能無償?shù)氐玫酱蠓忍嵘jP(guān)于這一點(diǎn),你可以將GWT mail sample的啟動(dòng)速度跟其他Ajax應(yīng)用范例做個(gè)比較。如果再在傳統(tǒng)web應(yīng)用和Ajax應(yīng)用間在部署差異加以關(guān)注的話,我們還可以進(jìn)一步提高應(yīng)用的性能。對(duì)于下一代的Ajax應(yīng)用,我充滿了期待。
【編輯推薦】