# 服务提供者接口简介

> 原文： [https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html](https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html)

## 什么是服务？

服务是声音处理功能的单元，当应用程序使用 Java Sound API 的实现时，这些功能可自动使用。它们由执行读取，写入，混合，处理和转换音频和 MIDI 数据的对象组成。 Java Sound API 的实现通常提供一组基本服务，但 API 中还包括机制，以支持第三方开发人员（或实现本身的供应商）开发新的声音服务。这些新服务可以“插入”现有的已安装实现，以扩展其功能，而无需新版本。在 Java Sound API 体系结构中，第三方服务以这样一种方式集成到系统中，即应用程序与它们的接口与“内置”服务的接口相同。在某些情况下，使用`javax.sound.sampled`和`javax.sound.midi`软件包的应用程序开发人员可能甚至不知道他们正在使用第三方服务。

潜在的第三方采样音频服务示例包括：

*   声音文件读者和作家
*   在不同音频数据格式之间转换的转换器
*   新的音频混合器和输入/输出设备，无论是纯粹用软件实现，还是用带软件接口的硬件实现

第三方 MIDI 服务可能包括：

*   MIDI 文件阅读器和编写器
*   各种类型音库文件的读者（通常特定于特定合成器）
*   MIDI 控制的声音合成器，音序器和 I / O 端口，无论是纯粹用软件实现，还是用带软件接口的硬件实现

## 服务如何运作

`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 平台运行时系统的功能。

安装新服务后，可以像以前安装的任何服务一样访问它。服务的消费者可以通过调用`AudioSystem`和`MidiSystem`类的方法（分别在`javax.sound.sampled`和`javax.sound.midi`包中）获取有关新服务的信息或获取新服务类本身的实例，以返回有关新服务的信息，或返回新的或现有服务类本身的实例。应用程序不需要 - 也不应该 - 直接引用 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 文件应包含 JAR 文件中支持的每个服务的类文件。遵循 Java 平台的约定，每个类文件都具有新定义的类的名称，该类是其中一个抽象服务提供者类的具体子类。 JAR 文件还必须包含新服务实现所需的任何支持类。为了可以通过运行时系统的服务提供程序机制定位新服务，JAR 文件还必须包含将 SPI 类名映射到正在定义的新子类的特殊文件（如下所述）。

继续上面的例子，A​​cme Software，Inc。正在分发一套新的采样音频服务。我们假设这个包包含两个新服务：

*   `AcmeAudioFileReader`类，如上所述，是`AudioFileReader`的子类
*   `AudioFileWriter`的子类称为`AcmeAudioFileWriter`，它将以 Acme 的新格式写入声音文件

从任意目录开始 - 让我们调用它`/devel` - 我们想要进行构建，我们创建子目录并将新类文件放入其中，以这样的方式组织，以便提供新类所需的路径名。被引用：

```
    com/acme/AcmeAudioFileReader.class
    com/acme/AcmeAudioFileWriter.class

```

另外，对于每个被子类化的新 SPI 类，我们在一个特别命名的目录`META-INF/services`中创建一个映射文件。该文件的名称是要子类的 SPI 类的名称，该文件包含该 SPI 抽象类的新子类的名称。

我们创建文件

```
  META-INF/services/javax.sound.sampled.spi.AudioFileReader

```

它由...组成

```
    # Providers of sound file-reading services 
    # (a comment line begins with a pound sign)
    com.acme.AcmeAudioFileReader

```

还有文件

```
  META-INF/services/javax.sound.sampled.spi.AudioFileWriter

```

它由...组成

```
    # Providers of sound file-writing services 
    com.acme.AcmeAudioFileWriter

```

现在我们使用命令行从任何目录运行`jar`：

```
jar cvf acme.jar -C /devel .

```

`-C`选项使`jar`切换到`/devel`目录，而不是使用执行命令的目录。 final period 参数指示`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

```

由`jar`实用程序本身生成的文件`Manifest.mf,`是存档中包含的所有文件的列表。

## 用户如何安装新服务

对于希望通过其应用程序访问新服务的最终用户（或系统管理员），安装很简单。他们将提供的 JAR 文件放在`CLASSPATH.`的目录中。执行时，Java 运行时将在需要时找到引用的类。

为同一服务安装多个提供程序并不是错误。例如，两个不同的服务提供商可能提供对读取相同类型的声音文件的支持。在这种情况下，系统任意选择一个提供者。关心选择哪个提供商的用户应该只安装所需的提供商。