Java SE 6.0(代號Mustang,野馬)已經發布,詳情請見
野馬奔騰而出,Java SE 6 正式版發布
,它給我們帶來了哪些新的特性了。
首先,我們看看JDK 6.0包含了大量的JSR,分為四組,分別為:
在簡化開發方面:
199: Compiler API
269: Annotation Processors
260: Javadoc™ Tag Update Ease of Development
221: JDBC™ 4.0
223: Scripting for the Java Platform
在XML方面:
105: XML Digital Signature(數字簽名)
173: Streaming API for XML XML
222: JAXB 2.0
在Web 服務方面
250: Common Annotations
181: WS Metadata Web Services
224: JAX-WS 2.0
其它:
202: Java Class 文件規范升級
詳情參見JSR 270,其鏈接為
http://jcp.org
除了制定相應的一系列的JSR之外,野馬所有的新特性是圍繞下面的目標展開的:
• 兼容性和穩定性(Compatibility and stability)
• 可診斷性,監控和管理(Diagnosability, monitoring, and management)
• 減輕開發量(Ease of development)
• 企業級桌面(Enterprise desktop)
• XML和Web 服務(XML and web services)
• 透明性(Transparency)
要實現這些目標,必然要增加不少代碼。其結果是JDK的個頭比原來的大了不少,安裝后JDK 6.0的大小為(169,346,858 字節);而JDK 5.0 update 7的大小為(124,808,838 字節)。
從源代碼結構上來看,增加了如下的新包:
包名
|
描述
|
java.text.spi
|
java.text包的服務提供者類
|
java.util.spi
|
java.util包的服務提供者類
|
javax.activation
|
激活框架
|
javax.annotation
|
標注處理支持
|
javax.jws
|
Web 服務支持類
|
javax.jws.soap
|
SOAP 支持類
|
javax.lang.model.*
|
支持編程語言的建模和語言元素與類型的處理
|
javax.script
|
Java 腳本語言引擎支持框架
|
javax.tools
|
提供類工具的訪問,譬如編譯器
|
javax.xml.bind.*
|
與 JAXB 相關的支持
|
javax.xml.crypto.*
|
與XML 密碼系統相關的支持
|
javax.xml.soap
|
支持建立和構建SOAP 消息
|
javax.xml.stream.*
|
支持XML
Streaming
API
|
javax.xml.ws.*
|
支持JAX-WS
|
在今年九月份的Sun Tech Day China上,Sun 的一個講師說,有十個可能忽視的有趣的東東,現列表如下:
10. 按需即附監視;
9. JConsole 插件 API;
8. jhat OQL (jmap heap dump);
7. Solaris 動態跟蹤(DTrace)支持(在Solaris OS下);
6. 由 javac 完成標注處理;
5. 類路徑(Class-path)*匹配;
4. 磁盤剩余空間 API;
3. 密碼提示;
2. Swing 新增布局管理器javax.swing.GroupLayout;
1. JDK 內置一個服器,JAX-WS 完成web 服務。
此為Java SE 6.0的新特性的開篇,今后將陸續就新特性進行詳細說明。
一、存取權限控制方法
從某種角度看,File類的一個實例其實是一個標識文件系統中文件或目錄對象的抽象路徑名。文件系統可以限制在這個對象上實現的讀、寫以及執行等操作。
讀、寫和執行限制統稱為“存取權限”。文件系統可以把多個存取權限集合關聯到單個對象。例如,一個集合可以用于對象的所有者而另一個集合可以用于所有的其他用戶。
前一個版本中提供的存取權限在直接用于存取對象時,有可能會導致File類的一些方法失敗。由于這個原因,Mustang為File類引入了六種新的方法以便讓你修改路徑名的存取權限:
①“public boolean setExecutable(boolean executable, boolean
ownerOnly)”:設置所有者或每個人對于指定抽象路徑名的執行許可權。當executable為true時,允許執行操作;而傳遞給它的值為
false時,則不允許執行。把true傳遞給參數ownerOnly僅允許該抽象路徑名的所有者擁有該許可權;當ownerOnly為false,則把
該許可權授予每個人。如果底層文件系統無法區分所有者的執行許可與每個人的執行許可,那么,該許可應用于每個人,而不管ownerOnly取值如何。
該方法在成功時返回true;否則,返回false。如果用戶無權改變抽象路徑名的存取權限或如果底層文件系統沒有實現一種執行許可并且executable為false,則方法調用失敗。
②“public boolean setExecutable(boolean executable)”:這個方法便于設置所有者對于給定抽象路徑名的執行權限。
③public oolean setReadable( oolean readable, oolean
ownerOnly)”:設置所有者或每個人對于這個抽象路徑名的讀取許可權。參數readable為true時允許讀取操作;否則,不允許讀取。參數
ownerOnly為true時僅允許該抽象路徑名的所有者擁有該許可權;當ownerOnly為false,則把該許可權授予每個人。如果底層文件系統
無法區分所有者的讀取許可與每個人的讀取許可,那么,該許可應用于每個人,而不管ownerOnly取值如何。
該方法在成功時返回true;否則,返回false。如果用戶無權改變抽象路徑名的存取權限或如果底層文件系統沒有實現一種讀取許可并且readable為false,則方法調用會失敗。
④“public boolean setReadable(boolean readable)”:這個方法便于設置所有者對于給定抽象路徑名的讀取權限。
⑤“public boolean setWritable(boolean writable,boolean
ownerOnly)”:設置所有者或每個人對于這個抽象路徑名的寫許可權。參數writable為true時允許寫操作;否則,不允許寫操作。參數
ownerOnly為true時僅允許該抽象路徑名的所有者擁有該許可權;當ownerOnly為false,則把該許可權授予每個人。如果底層文件系統
無法區分所有者的寫許可與每個人的寫許可,那么,該許可應用于每個人,而不管ownerOnly取值如何。
該方法在成功時這個方法返回true;否則,返回false。如果用戶無權改變抽象路徑名的存取權限,則方法調用會失敗。
⑥“public boolean setWritable(boolean writable)”:這個方法便于設置所有者對于給定抽象路徑名的寫權限。
【注意】如果存在一個安全管理器并且它的“public void checkWrite(String file)”方法不允許對文件進行寫操作的話,則上面列出的每一個方法都會拋出一個SecurityException異常。
File類還提供了如下對應的方法以幫助你獲得一個對象當前設置的讀、寫和執行權限:
①public boolean canRead();
②public boolean canWrite();
③public boolean canExecute()(在Mustang中新引入的)。
二、桌面集成
Sun的Java桌面開發小組引入了若干新的特征以進一步提高Java在桌面開發領域的影響。其中三個著名的特征是:Splash屏幕支持(它讓應用
程序在啟動過程中顯示Splash屏幕),系統托盤支持(它讓應用程序把圖標,提示窗信息和彈出菜單添加到系統托盤),和一組新的桌面API。
現在,我們來討論桌面API,它有助于無縫地把Java應用程序與桌面集成到一起。該API支持Java應用程序使用一個特定的統一資源標識符
(URI)啟動操作系統的缺省的瀏覽器;啟動OS的缺省的電子郵件客戶端;以及啟動應用程序以打開、編輯或打印與該應用程序相關聯的文件。
桌面API使用OS的文件關聯機制來啟動關聯到特定的文件類型的應用程序。例如,.doc文件擴展經常與微軟的Word關聯。經由桌面API,一個Java應用程序能夠啟動Word以打開、打印或編輯與這個擴展名相關聯的文件。
在啟動瀏覽器電子郵件客戶端或任何應用程序之前,你的Java應用程序必須決定你的OS是否支持該API。這一決定是通過調用
java.awt.Desktop類的“public static boolean
isDesktopSupported()”方法實現的。如果OS支持該API,這個方法返回true;否則,它返回false。
在調用isDesktopSupported()之后,該應用程序通過調用Desktop的“public static Desktop
getDesktop()”方法繼續檢索Desktop對象。如果OS不支持鍵盤輸入、顯示器或鼠標,這個方法將拋出一個
java.awt.Headless異常。如果OS不支持該桌面API,則拋出一個UnsupportedOperationException異常。
現在,既然該Java應用程序已經擁有了一個桌面實例,那么,按下來,它就能夠調用各種方法以瀏覽、發送郵件、打開、編輯或打印。在執行任何這些操作
之前,該程序可以調用Desktop的“public boolean isSupported(Desktop.Action
action)”方法,如果桌面支持該行為(被描述為適合的Desktop.Action枚舉實例),則這個方法返回true。這些
Desktop.Action枚舉如下:
· BROWSE:這個枚舉實例描述OS的缺省瀏覽器的瀏覽行為。
· MAIL:這個枚舉實例描述OS的缺省電子郵件客戶端的郵件行為。
· OPEN:這個枚舉實例描述與打開一個特定的文件類型相關聯的一個應用程序執行的打開行為。
· EDIT:這個枚舉實例描述與編輯一個特定的文件類型相關聯的一個應用程序執行的編輯行為。
· PRINT:這個枚舉實例描述與打印一個特定的文件類型相關聯的一個應用程序執行的打印行為。
【注意】在調用相應行為的Desktop方法前,你不必調用“isSupported(Desktop.Action
action)”來決定是否支持該行為:你可以直接調用相應的方法,但是之后,你必須處理該方法潛在地拋出的一個
UnsupportedOperationException異常。可以從Desktop存取下列行為方法:
①“public void browse(URI uri)”:啟動用戶缺省的瀏覽器以顯示一個URI—如果瀏覽器能夠處理這個URI的話;否則,它啟動該URI缺省的處理器應用程序(這具體要依賴于在java.net.URI類中定義的協議和路徑)。
如果uri為null,則拋出一個NullPointerException異常。如果用戶的缺省瀏覽器沒有找到或它沒能啟動或缺省的處理器應用程序沒能啟動,則拋出一個java.io.IOException異常。
②“public void edit(File file)”:啟動相關聯的編輯器應用程序并且打開一個文件進行編輯。
如果file為null,則拋出一個NullPointerException異常。如果指定的文件不存在,則拋出一個
IllegalArgumentException異常。最后,如果指定的文件相關聯的應用程序沒能啟動,或這個文件沒有相關聯的編輯器,則拋出一個
IOException異常。
③“public void mail()”:啟動用戶缺省的電子郵件客戶端的郵件編輯窗口。
如果用戶缺省的電子郵件客戶端沒有發現或啟動失敗,則拋出一個IOException異常。
④“public void mail(URI mailtoURI)”:啟動用戶缺省的電子郵件客戶端的郵件編輯窗口,填充由一個“mailto:”URI指定的消息域。這個URI能夠指定包括“cc”,“subject”和“body”在內的各種消息域。
如果mailtoURI為null,則拋出一個NullPointerException異常。如果URI的模式不是mailto,則拋出一個
IllegalArgumentException異常。如果用戶缺省的電子郵件客戶端沒有發現或啟動失敗,則拋出一個IOException異常。
⑤“public void open(File file)”:啟動相關聯的應用程序以打開該文件。如果指定的文件是一個目錄,則啟動OS的文件管理器以打開它。
如果file為
null,則拋出一個NullPointerException異常。如果指定的文件不存在,則拋出一個
IllegalArgumentException異常。最后,如果該指定的文件沒有相關聯的應用程序,或如果這個應用程序沒能啟動,則拋出一個
IOException異常。
⑥“public void print(File file)”:使用相關聯的應用程序的打印命令并使用本地桌面打印設備打印一個文件。
如果file為null,則拋出一個NullPointerException異常。如果指定的文件不存在,則拋出一個
IllegalArgumentException異常。如果指定的文件沒有相關聯的能夠用于打印其內容的應用程序,則拋出一個IOException異
常。
【注意】如果一個安全管理器存在并且不允許執行要求的行為的話,上面列出的每一個方法都會拋出一個SecurityException異常。
三、以編程方式存取網絡參數
Mustang提供了以編程方式存取網絡參數的方法—這是通過在java.net.NetworkInterface類和新的java.net.InterfaceAddress類中提供的10個新的方法實現的。這些新引入的網絡接口方法列舉如下:
①“public byte[]
getHardwareAddress()”:以一個字節數組形式返回這個網絡接口的硬件地址(通常是機器地址代碼,或MAC—也稱作以太網地址)。如果
這個接口沒有一個硬件地址,或如果不能存取這個地址(因為該用戶沒有足夠的權限),則返回null。如果發生一個I/O錯誤,則拋出一個
java.net.SocketException異常。
②“public List<InterfaceAddress>
getInterfaceAddresses()”:返回一個java.util.List,它包含這個網絡接口的所有接口地址(作為
InterfaceAddress實例)或其中一個子集。如果存在一個安全管理器,那么將使用相應于每一個接口地址的
java.net.InetAddress來調用它的一個checkConnect方法。注意,這個方法僅在列表中返回
InterfaceAddresses;而且此時,checkConnect并不拋出一個SecurityException異常。
③“public int
getMTU()”:返回這個網絡接口的最大傳輸單位(MTU)。該MTU指的是一個通訊協議層能夠傳遞到另一個層的最大包的大小(以字節為單位)。例
如,以太網允許的最大MTU是1500字節。根據某一標準(例如以太網)或連接時間(在端到端串行連接時,經常有這種情況),該MTU能夠被設置為一個固
定長度。如果發生一個I/O錯誤,則拋出一個SocketException異常。
④“public NetworkInterface
getParent()”:如果這個網絡接口是一個子接口,則返回這個網絡接口的父網絡接口(作為一個NetworkInterface實例)。然而,如
果這個網絡接口是一個物理(非虛擬的)接口或如果這個網絡接口是虛擬的并且沒有父接口,則返回null。
⑤“public Enumeration<NetworkInterface>
getSubInterfaces()”:返回一個包含所有的子接口(也稱為虛擬接口)的java.util.Enumeration(作為
NetworkInterface的實例)。例如,eth0:1是一個eth0的子接口(一個以太網網絡接口名)。
⑥“public boolean
isLoopback()”:返回true,如果這個網絡接口是一個loopback接口(一種網絡接口,在該接口中,外發數據作為輸入數據被立即反射回
該接口)。如果存在一個I/O問題,則拋出一個SocketException異常。
⑦“public boolean isPointToPoint()”:返回true,如果這個網絡接口是一個端到端的接口(例如一個通過調制解調器建立的PPP連接)。如果存在一個I/O問題,則拋出一個SocketException異常。
⑧“public boolean
isUp()”:返回true,如果這個網絡接口是“up”并且已經“running”。“up”指示已經為這個接口建立起了路由入口。
“running”指示要求的系統資源已經分配。如果存在一個I/O問題,則拋出一個SocketException異常。
⑨“public boolean isVirtual()”:返回true,如果這個網絡接口是一個虛擬接口(也稱作“子接口”)。如果存在一個I/O問題,則拋出一個SocketException異常。
⑩“public boolean
supportsMulticast()”:返回true,如果這個網絡接口支持多點傳送(指一個服務器程序把一個消息的一個副本發送給多個客戶端程序的
能力)。如果存在一個I/O問題,則拋出一個SocketException異常。
InterfaceAddress類描述了一個網絡接口地址。除了通常的實現獲得一個哈希代碼和獲得一個字符串描述等方法之外,這個類還提供了下列三個方法:
· “public InetAddress getAddress()”:返回給定接口地址:一個IP地址,一個子網掩碼和一個廣播地址(當該地址是IPv4時);或是一個IP地址和一個網絡前綴長度(針對一個IPv6地址)。
· “public InetAddress getBroadcast()”:返回給定接口地址的廣播地址(在IPv4網絡中作為一個InetAddress);在IPv6網絡中則返回null(此時它沒有廣播地址)。
· “public short getNetworkPrefixLength()”:返回給定接口地址的針對IPv6網絡的網絡前綴長度(或針對IPv4網絡的子網掩碼)。這個方法的返回值為一個短整型。
【注意】 典型的IPv6值是128(::1/128)或10(fe80::203:baff:fe27:1243/10)。針對IPv4的典型的值是8(255.0.0.0),16(255.255.0.0),或24(255.255.255.0)。
Java Web 涉及到的JSR有 105,173,181,222,224,250。
由于Web服務日趨流行,利用
Web服務的功能性的API特征正從最新的Java EE版本中向Java SE
6平臺遷移。換言之,針對Web服務不需另外加入額外的工具,在Java EE和Java
SE平臺擁有相同的API。野馬將大把不同的Web服務相關的API加到標準的工具柜中:以JSR 181針對Java
平臺的Web服務元數據,通過JSR 224的基于XML 的Web服務Java API(JAX-WS);針對Java的帶有附件的SOAP
API(SAAJ)作為JSR 67 。
與三個Web服務API相關的包新增到Java SE 6.0里:JAX-WS API
放置到javax.xml.ws包;
SAAJ類在javax.xml.soap 包;
Web服務的元數據類放置在javax.jws包里。
javax.jws 包
JSR
181 及其針對Java 平臺Web服務元數據的規范提供一個在類中利用標注設計和開發Web服務的機制。標注從J2SE 5.0引入,在Java
SE
6.0得到了進一步擴展。在第10章將會完整描述。但是,標注基本上允許將@tag加入到類,方法和特性來描述相關的元數據。一個解析器然后能定位標記并
采取適當的行動;盡管行動什么時候發生完全依賴于標記自身。
JAX-WS 2.0 很容易使用. 本文將介紹如何使用Java SE 6。以JAX-WS如何建立一個簡單的Web服務。首先建立一個建立一個將要作為Web服務發布的類,如表1所示:
Java 代碼 列表1
package
hello;


public
class
CircleFunctions
{


public
double
getArea(
double
radius)
{
return
java.lang.Math.PI
*
(r
*
r);
}
public
double
getCircumference(
double
radius)
{
return
2
*
java.lang.Math.PI
*
r;
}
}
為了輸出這些方法,必需做兩件事情:引入javax.jws.WebService
包;在類的前面增加@WebService
標注以告訴Java編譯器將發布此類為Web服務。下面的代碼顯示了這種變化(以黑體顯示增加的代碼)。
在此,有兩個基本的標注:@WebService和@WebMethod。@WebService標注指明HelloService 類作為Web服務。如果沒有制定,標注的名稱就是類的名稱。也能制定命名空間,服務名,WSDL 位置和endpoint 接口等。在指定的上下文環境中,可以使用javax.xml.ws.Endpoint類的publish()
靜態方法發布上面的類作為Web服務。代碼見列表3:
現在,用javac編譯源代碼。但是源文件能做什么呢?通過對源文件運行
javac編譯器進行編譯只是產生了class文件。并沒有產生特殊的東西。但是在編譯類之后,也需要運行wsgen 命令行工具(wsgen
是Web service generator的縮寫)。編譯源代碼后還必需完成更多的步驟:像下面一樣調用wsgen工具。
> wsgen –cp . hello.CircleFunctions
Wsgen工具將在一個叫做wsgen子目錄下產生大量的源代碼,然后將這些源代碼編譯成class文件。盡管從未編輯這些文件,但是可以瀏覽這些文件。注意在使用Wsgen工具時,原始的源代碼必需在相應的包里。否則將產生錯誤。
就這么簡單。當運行應用程序時,Java SE 6平臺擁有一個小的Web應用服務器,它將在地址為http://localhost:8080/WebServiceExample/circlefunctions
發布Web服務。通過顯示CircleFunction的WSDL 來驗證Web服務。當JVM仍然運行時,在瀏覽器輸入:
如果在瀏覽器里看見大量描述Web服務的功能的XML代碼,那么Web服務就成功發布了。
四、 表格排序與過濾
Swing的表格組件在若干方面得到了增強。其中的一個改進是,支持對一個表格中的行數據進行按升/降序排序并且能夠過濾掉其中某些行(所有數據來自于表格模型),并最終顯示在組件的視圖中。
【請記住】排序和過濾僅對視圖有影響,而對模型無影響。
排序和過濾基于一個新概念—行排序器對象,它能夠對行數據進行排序(和過濾)。把一個行排序器加入到一個表格組件中的最簡單的方法是調用
javax.swing.JTable中新引入的“public void setAutoCreateRowSorter(boolean
autoCreateRowSorter)”方法,下面的代碼片斷演示了它的用法:
TableModel model = createTableModel ();
JTable table = new JTable (model);
table.setAutoCreateRowSorter (true);
在每次改變模型時,把true傳遞給setAutoCreateRowSorter()能夠使JTable安裝一個新的
javax.swing.Table.TableRowSorter<M>實例作為行排序器。為了防止在以后改變模型時再創建新的行排序器,
可以把false傳給一個隨后調用的方法。
注意,當你不想定制行排序器時,你也有可能調用setAutoCreateRowSorter()。但是,在調用這個方法后,你仍然能夠定制行排序
器,這是通過首先調用JTable的新的“public RowSorter<? extends TableModel>
getRowSorter()”方法以返回當前行排序器來實現的。
因為當你試圖把返回的行排序器的引用存儲到一個TableRowSorter時編譯器會顯示一個未檢查的警告消息,所以,你可能更喜歡由你自己創建表
格行排序器并使用JTable的新的“public void setRowSorter(RowSorter<? extends
TableModel> sorter)”方法來安裝它:
TableRowSorter<TableModel> sorter;
sorter = new TableRowSorter<TableModel> (model);
table.setRowSorter (sorter);
對TableRowSorter的定制還包括能夠使用它的“public void setRowFilter(RowFilter<?
super M,? super I>
filter)”方法安裝一個行過濾對象(它基于某個標準接收行數據)。這個方法接收一個javax.swing.RowFilter<M,
I>參數,其相應的方法能夠返回不同種類的行過濾器。
有些行過濾器可以使用正規表達式。為了獲得這種行過濾器,可以調用“RowFilter public static <M,I>
RowFilter<M,I> regexFilter(String regex, int...
indices)”方法。例如,“sorter.setRowFilter (RowFilter.regexFilter
("^A"));”語句能夠創建一個行過濾器,它的“^A”正規表達式僅接受以A開始的行。
JTable還提供了其它一些與排序和行過濾有關的新方法。這其中的兩個是:“public int
convertRowIndexToModel(int viewRowIndex)”和“public int
convertRowIndexToView(int
modelRowIndex)”,它們分別負責把一個行的索引(根據模型)映射到視圖和把一個行的索引(根據視圖)映射到模型。
為了向你說明僅是視圖為排序和過濾所影響,我使用了前面的TableSortFilterDemo演示應用程序中的一個“convert”方法。在改
變行過濾器以后,除了把null傳遞給TableRowSorter的“public void setSortKeys(List<?
extends RowSorter.SortKey>
sortKeys)”方法以打亂視圖的排序外,列表4(見本文示例源程序)中其它的內容我們都已經討論過。
在編譯和運行這個應用程序后,通過點擊某一列的列頭部初始化一個排序。作為響應,所有的行按被點擊的列值重新升序或降序排列(每次點擊使之在升序與降序之間切換)。選擇的列和排序順序以相應列頭部的一個向上/向下的箭頭指示
。
除了排序之外,你還能夠安裝一個過濾器以決定在視圖中顯示哪些行。為此,只要在文本域中輸入一個正規表達式(例如^J或J),并且點擊“Set
Filter”按鈕即可。作為響應,所有的匹配該正規表達式的行都被以非排序方式顯示。然后,你可以再對這些行進行排序。
五
Security:安全性
Java SE 6的安全部分,增加了 XML-Digital Signature (XML-DSIG) APIs, 整合了GSS/Kerberos的操作API,LDAP上的JAAS認證。
六、 Java SE 6中的Java DB
雖然Java
DB僅占用2兆字節(MB)空間,它卻是一個先進的全事務處理的基于Java技術的數據庫,它支持各類開放標準、觸發器和存儲程序。Java
DB可以客戶端服務器模式使用,也可以直接嵌入到一個Java應用程序中。在這些場合,Java
DB都可以在同樣的Java虛擬機(JVM)中運行,這就無需在應用程序之外單獨購買、下載、安裝或管理這個數據庫。對于選擇在生產中采用Java
DB的客戶,Sun將提供支持服務。七 腳本語言與 Java(吳玥顥,目前就職于 IBM 中國開發中心 Harmony 開發團隊。 除了對 Java 和腳本語言的熱愛之外,他的興趣還包括哲學、神話、歷史與籃球。此外他還是個電腦游戲高手。您可以通過wuyuehao@cn.ibm.com聯系到他)
假設我們有一個簡單的需求,察看一份文檔中 5 個字母組成的單詞的個數。用 Java 一般實現如下:
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException;
public class Find5Words { public static void main(String[] args) throws IOException { String result = ""; String line = null; int num = 0; FileReader fr = new FileReader("filename"); BufferedReader br = new BufferedReader(fr); while ((line = br.readLine()) != null) { result += line; } br.close(); fr.close(); String[] s = result.split(" "); for (int i = 0; i < s.length; i++) { if (s[i].matches("^\\w{5}$")) { num++; } } System.out.println(num); } }
|
再看看 Perl 語言實現同樣功能的代碼:
open FILE, "<filename "; while (<FILE>) { for (split) { $num++ if /^\w{5}$/ } } print $num;
|
那么有沒有一種優雅的方式將 Java 與腳本語言結合呢,在今年秋季即將發布的 Java SE6(代號 Mustang)中,這將成為現實。
Mustang 的腳本引擎
JSR 233 為 Java
設計了一套腳本語言 API。這一套 API 提供了在 Java 程序中調用各種腳本語言引擎的接口。任何實現了這一接口的腳本語言引擎都可以在
Java 程序中被調用。在 Mustang 的發行版本中包括了一個基于 Mozilla Rhino 的 JavaScript 腳本引擎。
Mozilla Rhino
Rhino 是一個純 Java 的開源的 JavaScript 實現。他的名字來源于 O'Reilly 關于 JavaScript 的書的封面:
Rhino 項目可以追朔到 1997 年,當時 Netscape 計劃開發一個純 Java 實現的 Navigator,為此需要一個
Java 實現的 JavaScript —— Javagator。它也就是 Rhino 的前身。起初 Rhino 將 JavaScript
編譯成 Java
的二進制代碼執行,這樣它會有最好的性能。后來由于編譯執行的方式存在垃圾收集的問題并且編譯和裝載過程的開銷過大,不能滿足一些項目的需求,Rhino
提供了解釋執行的方式。隨著 Rhino 開放源代碼,越來越多的用戶在自己的產品中使用了 Rhino,同時也有越來越多的開發者參與了 Rhino
的開發并做出了很大的貢獻。如今 Rhino1.6R2 版本將被包含在 Java SE6 中發行,更多的 Java 開發者將從中獲益。
Rhino 提供了如下功能
- 對 JavaScript 1.5 的完全支持
- 直接在 Java 中使用 JavaScript 的功能
- 一個 JavaScript shell 用于運行 JavaScript 腳本
- 一個 JavaScript 的編譯器,用于將 JavaScript 編譯成 Java 二進制文件
支持的腳本語言
在dev.java.net可以找到官方的腳本引擎的實現項目。這一項目基于BSD License ,表示這些腳本引擎的使用將十分自由。目前該項目已對包括 Groovy, JavaScript, Python, Ruby, PHP 在內的二十多種腳本語言提供了支持。這一支持列表還將不斷擴大。
在 Mustang 中對腳本引擎的檢索使用了工廠模式。首先需要實例化一個工廠 —— ScriptEngineManager。
// create a script engine manager ScriptEngineManager factory = new ScriptEngineManager();
|
ScriptEngineManager 將在 Thread Context ClassLoader 的 Classpath 中根據 jar 文件的 META-INF 來查找可用的腳本引擎。它提供了 3 種方法來檢索腳本引擎:
// create engine by name ScriptEngine engine = factory.getEngineByName ("JavaScript"); // create engine by name ScriptEngine engine = factory.getEngineByExtension ("js"); // create engine by name ScriptEngine engine = factory.getEngineByMimeType ("application/javascript");
|
下面的代碼將會打印出當前的 JDK 所支持的所有腳本引擎
ScriptEngineManager factory = new ScriptEngineManager(); for (ScriptEngineFactory available : factory.getEngineFactories()) { System.out.println(available.getEngineName()); }
|
以下各章節代碼將以 JavaScript 為例。
在 Java 中解釋腳本
有了腳本引擎實例就可以很方便的執行腳本語言,按照慣例,我們還是從一個簡單的 Hello World 開始:
public class RunJavaScript { public static void main(String[] args){ ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName ("JavaScript"); engine.eval("print('Hello World')"); } }
|
這段 Java 代碼將會執行 JavaScript 并打印出 Hello World。如果 JavaScript 有語法錯誤將會如何?
engine.eval("if(true){println ('hello')");
|
故意沒有加上”}”,執行這段代碼 Java 將會拋出一個 javax.script.ScriptException 并準確的打印出錯信息:
Exception in thread "main" javax.script.ScriptException: sun.org.mozilla.javascript.internal.EvaluatorException: missing } in compound statement (<Unknown source>#1) in <Unknown source> at line number 1 at ...
|
如果我們要解釋一些更復雜的腳本語言,或者想在運行時改變該腳本該如何做呢?腳本引擎支持一個重載的 eval 方法,它可以從一個 Reader 讀入所需的腳本:
ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName ("JavaScript"); engine.eval(new Reader("HelloWorld.js"));
|
如此這段 Java 代碼將在運行時動態的尋找 HelloWorld.js 并執行,用戶可以隨時通過改變這一腳本文件來改變 Java 代碼的行為。做一個簡單的實驗,Java 代碼如下:
public class RunJavaScript { public static void main(String[] args) throws FileNotFoundException, ScriptException, InterruptedException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName ("JavaScript"); while (true) { engine.eval(new FileReader("HelloWorld.js")); Thread.sleep(1000); } } }
|
HelloWorld.js 內容為簡單的打印一個 Hello World:
print('Hello World');
運行 RunJavaScript 將會每一秒鐘打印一個 Hello World。這時候修改 HelloWorld.js 內容為
print('Hello Tony');
打印的內容將變為 Hello Tony,由此可見 Java 程序將動態的去讀取腳本文件并解釋執行。對于這一簡單的 Hello World
腳本來說,IO 操作將比直接執行腳本損失 20% 左右的性能(在我的 Think Pad
上),但他帶來的靈活性——在運行時動態改變代碼的能力,在某些場合是十分激動人心的。
腳本語言與 Java 的通信
ScriptEngine 的 put 方法用于將一個 Java 對象映射成一個腳本語言的變量。現在有一個 Java Class,它只有一個方法,功能就是打印一個字符串 Hello World:
package tony;
public class HelloWorld { String s = "Hello World"; public void sayHello(){ System.out.println(s); } }
|
那么如何在腳本語言中使用這個類呢?put 方法可以做到:
import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException;
public class TestPut { public static void main(String[] args) throws ScriptException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("JavaScript"); HelloWorld hello = new HelloWorld(); engine.put("script_hello", hello); engine.eval("script_hello.sayHello()"); } }
|
首先我們實例化一個 HelloWorld,然后用 put 方法將這個實例映射為腳本語言的變量 script_hello。那么我們就可以在
eval() 函數中像 Java 程序中同樣的方式來調用這個實例的方法。同樣的,假設我們有一個腳本函數,它進行一定的計算并返回值,我們在
Java 代碼中也可以方便的調用這一腳本:
package tony;
import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException;
public class TestInv { public static void main(String[] args) throws ScriptException, NoSuchMethodException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("JavaScript"); String script = "function say(first,second) { print(first +' '+ second); }"; engine.eval(script); Invocable inv = (Invocable) engine; inv.invokeFunction("say", "Hello", "Tony"); } }
|
在這個例子中我們首先定義了一個腳本函數 say,它的作用是接受兩個字符串參數將他們拼接并返回。這里我們第一次遇到了
ScriptEngine 的兩個可選接口之一 —— Invocable,Invocable 表示當前的 engine
可以作為函數被調用。這里我們將 engine 強制轉換為 Invocable 類型,使用 invokeFunction
方法將參數傳遞給腳本引擎。invokeFunction這個方法使用了可變參數的定義方式,可以一次傳遞多個參數,并且將腳本語言的返回值作為它的返回
值。下面這個例子用JavaScript實現了一個簡單的max函數,接受兩個參數,返回較大的那個。為了便于斷言結果正確性,這里繼承了JUnit
Testcase,關于JUnit請參考www.junit.org。
package tony;
import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import junit.framework.TestCase;
public class TestScripting extends TestCase {
public void testInv() throws ScriptException, NoSuchMethodException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("JavaScript"); String script = "function max(first,second) " + "{ return (first > second) ?first:second;}"; engine.eval(script); Invocable inv = (Invocable) engine; Object obj = inv.invokeFunction("max", "1", "0"); assertEquals("1", obj.toString()); } }
|
Invocable 接口還有一個方法用于從一個 engine 中得到一個 Java Interface 的實例,它的定義如下:
<T> T getInterface(Class<T> clasz)
|
它接受一個 Java 的 Interface 類型作為參數,返回這個 Interface 的一個實例。也就是說你可以完全用腳本語言來寫一個 Java Interface 的所有實現。以下是一個例子。
首先定一了個 Java Interface,它有兩個簡單的函數,分別為求最大值和最小值:
package tony;
public interface MaxMin { public int max(int a, int b); public int min(int a, int b); }
|
這個 Testcase 用 JavaScript 實現了 MaxMin 接口,然后用 getInterface 方法返回了一個實例并驗證了結果。
public void testInvInterface() throws ScriptException, NoSuchMethodException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("JavaScript"); String script = "function max(first,second) " + "{ return (first > second) ?first:second;}"; script += "function min(first,second) { return (first < second) ?first:second;}"; engine.eval(script); Invocable inv = (Invocable) engine; MaxMin maxMin = inv.getInterface(MaxMin.class); assertEquals(1, maxMin.max(1, 0)); assertEquals(0, maxMin.min(1, 0)); }
|
腳本的編譯執行
到目前為止,我們的腳本全部都是解釋執行的,相比較之下編譯執行將會獲得更好的性能。這里將介紹 ScriptEngine 的另外一個可選接口
—— Compilable,實現了這一接口的腳本引擎支持腳本的編譯執行。下面這個例子實現了一個判斷給定字符串是否是 email 地址或者 ip
地址的腳本:
public void testComplie() throws ScriptException { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("JavaScript"); String script = "var email=/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]" + "+(\\.[a-zA-Z0-9_-]+)+$/;"; script += "var ip = /^(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])" +"(\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])){3}$/;"; script += "if(email.test(str)){println('it is an email')}" + "else if(ip.test(str)){println('it is an ip address')}" + "else{println('I don\\'t know')}"; engine.put("str", "email@address.tony"); Compilable compilable = (Compilable) engine; CompiledScript compiled = compilable.compile(script); compiled.eval(); }
|
腳本編譯的過程如下:首先將 engine 轉換為 Compilable 接口,然后調用 Compilable 接口的 compile
方法得到一個 CompiledScript 的實例,這個實例就代表一個編譯過的腳本,如此用 CompiledScript 的 eval
方法即為調用編譯好的腳本了。在我的 Think Pad 上,這段代碼編譯后的調用大約比直接調用 engine.eval 要快 3-4
倍。隨著腳本復雜性的提升,性能的提升會更加明顯。
腳本上下文與綁定
真正將腳本語言與 Java 聯系起來的不是 ScriptEngine,而是 ScriptContext,它作為 Java 與 ScriptEngine 之間的橋梁而存在。
一個 ScriptEngine 會有一個相應的 ScriptContext,它維護了一個 Map,這個 Map
中的每個元素都是腳本語言對象與 Java 對象之間的映射。同時這個 Map 在我們的 API 中又被稱為 Bindings。一個
Bindings 就是一個限定了 key 必須為 String 類型的 Map —— Map<String,
Object>。所以一個 ScriptContext 也會有對應的一個 Bindings,它可以通過 getBindings 和
setBindings 方法來獲取和更改。
一個 Bindings 包括了它的 ScriptContext 中的所有腳本變量,那么如何獲取腳本變量的值呢?當然,從 Bindings
中 get 是一個辦法,同時 ScriptContext 也提供了 getAttribute
方法,在只希望獲得某一特定腳本變量值的時候它顯然是十分有效的。相應地 setAttribute 和 removeAttribute
可以增加、修改或者刪除一個特定變量。
在 ScriptContext 中存儲的所有變量也有自己的作用域,它們可以是 ENGINE_SCOPE 或者是
GLOBAL_SCOPE,前者表示這個 ScriptEngine 獨有的變量,后者則是所有 ScriptEngine 共有的變量。例如我們執行
engine.put(key, value) 方法之后,這時便會增加一個 ENGINE_SCOPE 的變量,如果要定義一個
GLOBAL_SCOPE 變量,可以通過 setAttribute(key, value, ScriptContext.GLOBAL_SCOPE)
來完成。
此外 ScriptContext 還提供了標準輸入和輸出的重定向功能,它可以用于指定腳本語言的輸入和輸出。
在 JavaScript 中使用 Java 高級特性
這一部分不同于前述內容,將介紹 JavaScript引擎 —— Rhino 獨有的特性。
使用 Java 對象
前面的部分已經介紹過如何在 JavaScript 中使用一個已經實例化的 Java 對象,那么如何在 JavaScript
中去實例化一個 Java 對象呢?在 Java 中所有 Class 是按照包名分層次存放的,而在 JavaScript
沒有這一結構,Rhino 使用了一個巧妙的方法實現了對所有 Java 對象的引用。Rhino 中定義了一個全局變量——
Packages,并且它的所有元素也是全局變量,這個全局變量維護了 Java 類的層次結構。例如 Packages.java.io.File
引用了 Java 的 io 包中 File 對象。如此一來我們便可以在 JavaScript 中方便的使用 Java 對象了,new 和
Packages 都是可以被省略的:
//The same as: var frame = new Packages.java.io.File("filename"); var frame = java.io.File("filename");
|
我們也可以像 Java 代碼中一樣把這個對象引用進來:
importClass (java.io.File); var file = File("filename");
|
如果要將整個包下的所有類都引用進來可以用 importPackage:
如果只需要在特定代碼段中引用某些包,可以使用 JavaImporter 搭配 JavaScript 的 with 關鍵字,如:
var MyImport = JavaImporter(java.io.File); with (MyImport) { var myFile = File("filename"); }
|
用戶自定義的包也可以被引用進來,不過這時候 Packages 引用不能被省略:
importPackage(Packages.tony); var hello = HelloWorld(); hello.sayHello();
|
注意這里只有 public 的成員和方法才會在 JavaScript 中可見,例如對 hello.s 的引用將得到 undefined。下面簡單介紹一些常用的特性:
使用 Java 數組
需要用反射的方式構造:
var a = java.lang.reflect.Array.newInstance(java.lang.String, 5);
|
對于大部分情況,可以使用 JavaScript 的數組。將一個 JavaScript 的數組作為參數傳遞給一個 Java 方法時 Rhino 會做自動轉換,將其轉換為 Java 數組。
實現一個 Java 接口
除了上面提到的 Invocable 接口的 getInterface 方法外,我們也可以在腳本中用如下方式:
//Define a JavaScript Object which has corresponding method obj={max:function(a,b){return (a > b) ?a:b;}}; //Pass this object to an Interface maxImpl=com.tony.MaxMin(obj); //Invocation print (maxImpl.max(1,2));
|
如果接口只有一個方法需要實現,那么在 JavaScript 中你可以傳遞一個函數作為參數:
function func(){ println("Hello World"); } t=java.lang.Thread(func); t.start();
|
對于 JavaBean 的支持
Rhino 對于 JavaBean 的 get 和 is 方法將會自動匹配,例如調用 hello.string,如果不存在 string
這個變量,Rhino 將會自動匹配這個實例的 isString 方法然后再去匹配 getString 方法,若這兩個方法均不存在才會返回
undefined。
命令行工具 jrunscript
在 Mustang 的發行版本中還將包含一個腳本語言的的命令行工具,它能夠解釋所有當前 JDK 支持的腳本語言。同時它也是一個用來學習腳本語言很好的工具。你可以在http://java.sun.com/javase/6/docs/technotes/tools/share/jrunscript.html找到這一工具的詳細介紹。
結束語
腳本語言犧牲執行速度換來更高的生產率和靈活性。隨著計算機性能的不斷提高,硬件價格不斷下降,可以預見的,腳本語言將獲得更廣泛的應用。在
JavaSE 的下一個版本中加入了對腳本語言的支持,無疑將使 Java 程序變得更加靈活,也會使 Java 程序員的工作更加有效率。
參考資料
posted on 2007-02-08 11:18
wqwqwqwqwq 閱讀(2037)
評論(1) 編輯 收藏 所屬分類:
SUN Tech