Java很早就推出了Java Web Start(簡(jiǎn)稱JWS)技術(shù)。這一技術(shù)的初衷很好:希望將桌面程序和Web頁(yè)面之間搭起一個(gè)無(wú)縫的橋梁。雖然Applet技術(shù)已經(jīng)存在了十多年,但是它日趨老邁衰落,所以JWS也就應(yīng)運(yùn)而生了。
但是JWS并未順利實(shí)現(xiàn)它的初衷。從Java的幾次大改版都可以看到,JWS的bug多多,漏洞頻頻,Sun和Oracle不得不頻繁的進(jìn)行打補(bǔ)丁修復(fù)。可以看看Java 5和6每次大小版本升級(jí)變化中,有多少是和Java Web Start有關(guān)的。難怪很多人都這樣感嘆:“哥再也不用Java Web Start部署應(yīng)用了!”其實(shí)也未必,隨著Java的不斷完善,我們只要了解更多的技巧,就可以有效的消除一些JWS潛在的問(wèn)題,并順利的應(yīng)用在企業(yè)應(yīng)用中。
以2BizBox ERP項(xiàng)目為例,本文介紹如何在企業(yè)應(yīng)用中利用動(dòng)態(tài)生成JNLP文件的技術(shù)來(lái)實(shí)現(xiàn)應(yīng)用的快速部署。
大家知道,2BizBox ERP作為一個(gè)免費(fèi)的高質(zhì)量ERP軟件,有成千上萬(wàn)的用戶。就我們開發(fā)團(tuán)隊(duì)負(fù)責(zé)維護(hù)的服務(wù)器,就有近千臺(tái)。每臺(tái)服務(wù)器都是一家企業(yè),每家企業(yè)又有幾十上百的客戶端。如果采用下載客戶端安裝程序進(jìn)行安裝的方式來(lái)維護(hù)諸多的客戶端,無(wú)疑是巨大的工作量,用戶和我們開發(fā)團(tuán)隊(duì)都不會(huì)輕松方便。為了解決這一問(wèn)題,采用JWS無(wú)疑是必然的選擇。
為了讓客戶端自動(dòng)啟動(dòng)下載和安裝程序,我們?cè)谄髽I(yè)的2BizBox ERP服務(wù)器上部署以下JNLP文件內(nèi)容:
1 2<?xml version="1.0" encoding="utf-8"?>
3 <jnlp spec="1.0+" codebase="http://**.**.**.**/webstart/">
4 <information>
5 <title>2BizBox</title>
6 <vendor>Serva Software</vendor>
7 <homepage />
8 <description>2BizBox ERP 3</description>
9 <offline-allowed/>
10 </information>
11 <security>
12 <all-permissions/>
13 </security>
14 <update check="always" policy="always"/>
15 <resources>
16 <j2se />
17 <jar href="2bizbox.jar />
18 <jar href="lib1.jar />
19 <jar href="lib2.jar />
20 <jar href="lib3.jar />
21 <jar href="lib4.jar />
22 <!-- more jar

. -->
23 </resources>
24 <application-desc main-class="com.serva.bb2.gui.Main">
25 <argument>**.**.**.**</argument>
26 </application-desc>
27 </jnlp>
上面的JNLP文件定義了2BizBox ERP客戶端啟動(dòng)所需要的jar包以及下載位置、jre版本等。
在實(shí)際應(yīng)用中,效果良好。但是由于JNLP和JWS本身的bug,在某些情況下,后臺(tái)jar程序更新升級(jí)后,用戶側(cè)啟動(dòng)jnlp并不能獲得更新,需要強(qiáng)行清空J(rèn)WS緩存才行,這肯定不是一般用戶懂得的。還有一種情況,就是由于ERP本身的jar包發(fā)生了變化(例如發(fā)生了增減),此時(shí)相當(dāng)于jnlp文件的內(nèi)容發(fā)生了變化。這時(shí)候,要求用戶一側(cè)機(jī)器必須意識(shí)到j(luò)nlp的變化并先將jnlp進(jìn)行更新。在很多java版本中(例如jre6的早期版本——例如jre6 update20之前),由于潛在的一些bug等原因,都不能順利的進(jìn)行更新,導(dǎo)致程序啟動(dòng)失敗。
如何解決這一情況呢?采用動(dòng)態(tài)jnlp是一個(gè)有效的方法。
動(dòng)態(tài)jnlp的思路是:在服務(wù)器的后端,通過(guò)jsp或servlet來(lái)動(dòng)態(tài)的生成一個(gè)jnlp文件,而不是放置一個(gè)靜態(tài)的固定不變的jnlp文件。這樣,jnlp文件內(nèi)容就可以通過(guò)后臺(tái)應(yīng)用的邏輯進(jìn)行動(dòng)態(tài)生成創(chuàng)建:需要什么jar包、需要什么jre版本等等。
以jsp為例。在這個(gè)jsp中,首先要注意的幾個(gè)技術(shù)點(diǎn)是:要設(shè)置本頁(yè)面不要被瀏覽器緩存,放置jnlp內(nèi)容變化無(wú)法及時(shí)被更新;其次要設(shè)置mime類型讓瀏覽器認(rèn)為它是一個(gè)jnlp文件,以便下載執(zhí)行而不是直接在瀏覽器中顯示出來(lái)。通過(guò)設(shè)置response即可達(dá)到這些目的:
1 response.setHeader("Pragma", "no-cache");
2 response.setHeader("Expires", "0");
3 response.setHeader("Content-Disposition", "filename=\"bb.jnlp\";");
4 response.setContentType("application/x-java-jnlp-file");
其中,禁止瀏覽器和webstart緩存jnlp內(nèi)容,通過(guò)設(shè)置:response.setHeader("Pragma", "no-cache");和response.setHeader("Expires", "0");
設(shè)置文件類型,并給定一個(gè)動(dòng)態(tài)的文件名。這個(gè)通過(guò)這個(gè)進(jìn)行:response.setHeader("Content-Disposition", "filename=\"bb.jnlp\";");response.setContentType("application/x-java-jnlp-file");
一個(gè)需要注意的問(wèn)題是,在動(dòng)態(tài)生成jnlp文件時(shí),要注意
jnlp文件中的href標(biāo)簽不要進(jìn)行設(shè)置。為什么呢?看一下jnlp的格式文檔是這樣說(shuō)的:
http://lopica.sourceforge.net/ref.html#jnlp
The jnlp file's one and only root.
Attributes
spec=version , optional
Specifies what versions of the jnlp spec a jnlp file works with. The default value is 1.0+. Thus, you can typically leave it out.
version=version , optional
Specifies the version of the application as well as the version of the jnlp file itself.
codebase=url , optional
Specifies the codebase for the application. Codebase is also used as base URL for all relative URLs in href attributes.
href=url , optional
Contains the location of the jnlp file as a URL. If you leave out the href attribute, Web Start will disable the update check on your JNLP file, and Web Start will not treat each new JNLP file as an application update - only updated jar files will. Leaving out href usually makes only sense if your jnlp file is created dynamically (that is, throug a cgi-script, for example) and if your jnlp file's arguments or properties change from request to request (user to user).
Note, that Java Web Start needs href to list your app in the Web Start Application Manager.
可見(jiàn)在動(dòng)態(tài)生成jnlp時(shí)候就不要設(shè)置href了,這樣就可以保證每次瀏覽器會(huì)重新下載jnlp文件內(nèi)容,否則可能會(huì)被緩存,無(wú)法及時(shí)更新程序。
另外一個(gè)技巧是:jnlp文件中的jar包,可以進(jìn)行動(dòng)態(tài)檢查文件jar包并動(dòng)態(tài)生成。這樣,如果以后程序的jar文件有增減,就不必修改jnlp文件了。方法也很簡(jiǎn)單:檢查當(dāng)前web在服務(wù)器的絕對(duì)路徑,并list所有的jar文件,然后在jnlp生成時(shí)候輸出即可:
1
2 <%
3 String urlString=request.getRequestURL().toString();
4 URL url=new URL(urlString);
5 String host=url.getHost();
6 String path = request.getSession().getServletContext().getRealPath("/");
7 path=path.replace("\\.\\", "\\");
8 File file=new File(path);
9 String[] files = file.list();
10 ArrayList jarNames=new ArrayList();
11 for(int i=0;i<files.length;i++){
12 String fileName=files[i];
13 if(fileName.toLowerCase().endsWith(".jar")){
14 jarNames.add(fileName);
15 }
16 }
17 %>
18
然后在jar的部分這樣列出:
1 <resources>
2 <j2se />
3 <%
4 for(int i=0;i<jarNames.size();i++){
5 out.write("\n");
6 out.write("<jar href=\""+jarNames.get(i).toString()+"\"/>");
7 }
8 %>
9 </resources>
10
最后,如果需要在jnlp中指定當(dāng)前服務(wù)器的ip地址或主機(jī)地址,也可以通過(guò)動(dòng)態(tài)生成。例如jnlp文件中的codebase,就是如此。另外,2BizBox ERP還需要在主函數(shù)中給出當(dāng)前服務(wù)器的ip地址。而對(duì)于上千家的2BizBox服務(wù)器,每個(gè)jnlp要手工維護(hù)ip地址,是不可想象的。這里通過(guò)動(dòng)態(tài)生成,就永遠(yuǎn)的解決了這個(gè)問(wèn)題:
1 String urlString=request.getRequestURL().toString();
2 URL url=new URL(urlString);
3 String host=url.getHost();
然后在jnlp中:
1 <jnlp spec="1.0+" codebase="http://<%=host%>/webstart/">
2
3 <application-desc main-class="com.serva.bb2.gui.Main">
4 <argument><%=host%></argument>
5 </application-desc>
這樣,通過(guò)jsp動(dòng)態(tài)生成jnlp的方案就完成了。它在2BizBox ERP中應(yīng)用良好,方便的讓上千家2BizBox ERP的云主機(jī)用戶快速得到程序更新,而簡(jiǎn)化了程序的維護(hù)方式。