Java6 MUstang新特性总结(摘录)

Java6 MUstang新特性总结(摘录)
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服务。下面的代码显示了这种变化(以黑体显示增加的代码)。

Java 代码 列表2
package  hello;

import  javax.jws.WebService;

@WebService

public   class  CircleFunctions  {
   
public double getArea(double r) {
       
return java.lang.Math.PI * (r * r);
    }


   
public double getCircumference(double r) {
        
return 2 * java.lang.Math.PI * r;
    }


}



在此,有两个基本的标注@WebService@WebMethod。@WebService标注指明HelloService 类作为Web服务。如果没有制定,标注的名称就是类的名称。也能制定命名空间,服务名,WSDL 位置和endpoint 接口等。在指定的上下文环境中,可以使用javax.xml.ws.Endpoint类的publish()静态方法发布上面的类作为Web服务。代码见列表3:

Java 代码 列表3

import  javax.xml.ws.Endpoint;

public   static   void  main(String[] args)  {

      Endpoint.publish(
         
"http://localhost:8080/WebServiceExample/circlefunctions",
         
new CircleFunctions());
        
try {
            System.in.read();
        }
 catch (IOException ex) {
            ex.printStackTrace();
        }

}



现在,用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仍然运行时,在浏览器输入:

  http://ocalhost:8080/WebServiceExample/circlefunctions?WSDL    

如果在浏览器里看见大量描述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 和脚本语言的热爱之外,他的兴趣还包括哲学、神话、历史与篮球。此外他还是个电脑游戏高手。您可以通过 [email protected]联系到他

假设我们有一个简单的需求,察看一份文档中 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 的书的封面:

Java6 MUstang新特性总结(摘录)_第1张图片

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 protected]");
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:

importPackage(java.io);

如果只需要在特定代码段中引用某些包,可以使用 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 程序员的工作更加有效率。



参考资料

  • JSR 233 主页http://www.jcp.org/en/jsr/detail?id=223 你可以在这里找到关于这个 Request 的详细信息。

  • Mozilla Rhino 项目主页http://www.mozilla.org/rhino/ 这里有完善的项目文档,并且可以下载到该项目的最新发行版本以及源代码。

  • https://scripting.dev.java.net/是官方脚本引擎项目的主页,在这里可以查看到已经对哪些脚本引擎提供了支持。并且可以找到相应脚本引擎的网页链接。

  • http://www.opensource.org/licenses/ 在这里可以找到关于 Open source license 的详细信息。

  • http://www.junit.org/ 是 Junit 的官方站点

  • http://java.sun.com/javase/6/docs/是 Java SE6 的在线文档


你可能感兴趣的:(Java6 MUstang新特性总结(摘录))