本教程适用于JDK 8。本页面中描述的示例和实践不利用后续版本引入的改进,并且可能使用已不再可用的技术。
请参阅Java语言变更以获取Java SE 9及后续版本中更新的语言特性摘要。
请参阅JDK发布说明以获取有关所有JDK版本的新功能、增强功能和已删除或不推荐使用选项的信息。
服务是在应用程序使用Java Sound API的实现时自动提供的声音处理功能单元。它们由读取、写入、混合、处理和转换音频和MIDI数据的对象组成。Java Sound API的实现通常提供一组基本的服务,但API中还包括支持第三方开发者(或实现供应商自身)开发新的声音服务的机制。这些新服务可以“插入”到现有的安装实现中,以扩展其功能而无需发布新版本。在Java Sound API体系结构中,第三方服务被集成到系统中的方式是,应用程序对它们的接口与对“内置”服务的接口相同。在某些情况下,使用javax.sound.sampled和javax.sound.midi包的应用程序开发人员甚至可能不知道他们正在使用第三方服务。
潜在的第三方采样音频服务的示例包括:
第三方MIDI服务可能包括:
javax.sound.sampled和javax.sound.midi包提供了功能,供希望在其应用程序中包含声音服务的应用程序开发人员使用。这些包是声音服务的消费者,提供了获取有关音频和MIDI服务的信息、控制和访问这些服务的接口。此外,Java Sound API还提供了两个包,定义了由声音服务的提供者使用的抽象类:javax.sound.sampled.spi和javax.sound.midi.spi包。
新声音服务的开发者实现SPI包中适当类的具体子类。这些子类连同支持新服务所需的任何其他类,被放置在一个Java Archive (JAR)档案文件中,其中包含了所包含服务的描述。当这个JAR文件被安装在用户的CLASSPATH中时,运行时系统会自动使新服务可用,扩展Java平台的运行时系统的功能。
一旦新服务被安装,它可以像之前安装的服务一样被访问。服务的消费者可以通过调用javax.sound.sampled和javax.sound.midi包中的AudioSystem和MidiSystem类的方法来获取有关新服务的信息,或者获取新服务类的实例本身,以返回有关新服务的信息或返回新的或现有的服务类的实例。应用程序无需直接引用SPI包中的类(及其子类)即可使用安装的服务。
例如,假设一个名为Acme Software,Inc.的虚拟服务提供商有兴趣提供一个包,允许应用程序读取一种新的声音文件格式(但其音频数据采用标准数据格式)。SPI类AudioFileReader
可以被继承为一个名为AcmeAudioFileReader
的类。在新的子类中,Acme将提供AudioFileReader
中定义的所有方法的实现;在这种情况下,只有两个方法(带有参数变体),即getAudioFileFormat
和getAudioInputStream
。然后,当应用程序尝试读取一个恰好是Acme文件格式的声音文件时,它将调用javax.sound.sampled
中AudioSystem
类的方法来访问该文件和关于该文件的信息。方法AudioSystem.getAudioInputStream
和AudioSystem.getAudioFileFormat
提供了一个标准API来读取音频流;安装了AcmeAudioFileReader
类后,该接口将被扩展以透明地支持新的文件类型。应用程序开发人员不需要直接访问新注册的SPI类:AudioSystem
对象方法将查询传递给已安装的AcmeAudioFileReader
类。
拥有这些“工厂”类的意义是什么?为什么不允许应用程序开发人员直接访问新提供的服务?这是一个可能的方法,但是通过门禁系统对象传递所有服务的管理和实例化,可以使应用程序开发人员无需了解已安装服务的身份。应用程序开发人员只使用对他们有价值的服务,甚至可能都不知道。与此同时,该架构允许服务提供商有效地管理其包中的可用资源。
对于应用程序,使用新的音频服务通常是透明的。例如,想象一种情况,应用程序开发人员希望从文件中读取一个音频流。假设thePathName
标识一个音频输入文件,程序可以这样做:
File theInFile = new File(thePathName); AudioInputStream theInStream = AudioSystem.getAudioInputStream(theInFile);
在幕后,AudioSystem
确定哪个已安装的服务可以读取该文件,并要求其提供音频数据作为AudioInputStream
对象。开发人员可能不知道或者甚至不关心输入音频文件是某种新的文件格式(如Acme的格式),并且该格式由已安装的第三方服务支持。程序与流的第一次接触是通过AudioSystem
对象,其后对流及其属性的所有后续访问都通过AudioInputStream
的方法进行。这两者都是javax.sound.sampled
API中的标准对象;新的文件格式可能需要的特殊处理完全隐藏起来。
服务提供者通过特殊格式的JAR文件提供他们的新服务,这些文件需要安装在用户系统中Java运行时能够找到的目录中。JAR文件是存档文件,每个文件都包含一组可能以层次结构组织在存档中的文件。有关放入这些存档中的类文件的准备细节将在接下来的几页中讨论,这些页面描述了音频和MIDI SPI包的具体情况;在这里,我们只提供JAR文件创建过程的概述。
新服务或服务的JAR文件应包含每个在该文件中支持的服务的类文件。按照Java平台的约定,每个类文件的名称为新定义的类的名称,这是一个抽象服务提供者类的具体子类。JAR文件还必须包含新服务实现所需的任何支持类。为了使运行时系统的服务提供者机制能够定位到新服务或服务,JAR文件还必须包含特殊文件(下面会描述)来将SPI类名称映射到正在定义的新子类。
继续上面的示例,假设Acme Software, Inc.正在分发一个新的采样音频服务包。假设该包包含两个新服务:
AcmeAudioFileReader
类,如上所述,它是AudioFileReader
的子类AcmeAudioFileWriter
的AudioFileWriter
的子类,它将以Acme的新格式写入声音文件从一个任意的目录开始 - 让我们称之为/devel
- 我们创建子目录并将新的类文件放在其中,以所需的路径名引用新类:
com/acme/AcmeAudioFileReader.class com/acme/AcmeAudioFileWriter.class
此外,对于每个被子类化的新SPI类,我们在一个名为META-INF/services
的特殊命名目录中创建一个映射文件。文件的名称是被子类化的SPI类的名称,文件包含该SPI抽象类的新子类的名称。
我们创建文件
META-INF/services/javax.sound.sampled.spi.AudioFileReader
它包含以下内容
# 提供声音文件读取服务的提供者 # (注释行以井号开始) com.acme.AcmeAudioFileReader
还有文件
META-INF/services/javax.sound.sampled.spi.AudioFileWriter
它包含以下内容
# 提供声音文件写入服务的提供者 com.acme.AcmeAudioFileWriter
现在我们可以从任何目录下使用命令行运行jar
:
jar cvf acme.jar -C /devel .
-C
选项会让jar
切换到/devel
目录,而不是使用当前执行命令的目录。最后的点号参数告诉jar
将该目录(即/devel
)中的所有内容归档,但不包括目录本身。
此次运行将创建文件acme.jar
,其中包含以下内容:
com/acme/AcmeAudioFileReader.class com/acme/AcmeAudioFileWriter.class META-INF/services/javax.sound.sampled.spi.AudioFileReader META-INF/services/javax.sound.sampled.spi.AudioFileWriter META-INF/Manifest.mf
Manifest.mf
文件是由jar
工具自动生成的,其中列出了存档中的所有文件。
对于希望通过应用程序获取新服务的最终用户(或系统管理员),安装非常简单。他们将提供的JAR文件放置在CLASSPATH
中的一个目录中。在执行时,Java运行时将在需要时找到所引用的类。
为同一个服务安装多个提供者不会出错。例如,两个不同的服务提供者可能提供对同一类型的声音文件的支持。在这种情况下,系统会任意选择一个提供者。关心所选择的提供者的用户应该只安装所需的那个。