一、何谓注解?
Annotation
(注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。
注解本质是一个继承了Annotation
的特殊接口:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}public interface Override extends Annotation{}
JDK 提供了很多内置的注解(比如 @Override
、@Deprecated
),同时,我们还可以自定义注解。
二、什么是SPI?
SPI 即 Service Provider Interface ,字面意思就是:“服务提供者的接口”,我的理解是:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。
SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。
很多框架都使用了 Java 的 SPI 机制,比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。
三、SPI和 API有什么区别?
- 定义
- SPI(Service Provider Interface)
- SPI 是一种服务提供接口,主要用于框架扩展或插件式的开发。它允许第三方开发者通过实现接口来提供服务,而这些服务可以被框架发现并集成。例如,在 Java 的数据库驱动中,JDBC(Java Database Connectivity)就是一种 SPI。不同的数据库厂商(如 MySQL、Oracle 等)可以实现 JDBC 规定的接口,从而让 Java 程序能够以统一的方式访问不同的数据库。
- API(Application Programming Interface)
- API 是应用程序编程接口,它定义了软件组件之间如何交互。通常是一组函数、协议或工具,用于构建软件应用程序。比如,我们使用的地图应用程序,它可能会提供 API 给开发者。开发者可以利用这些 API 在自己的应用中嵌入地图功能,像获取地理位置、显示地图、规划路线等功能都可以通过调用地图应用的 API 来实现。
- SPI(Service Provider Interface)
- 使用目的
- SPI
- 侧重于服务的提供和扩展。它使得框架能够灵活地加载不同的服务实现,而不需要在框架内部硬编码具体的服务逻辑。以日志框架为例,通过 SPI,我们可以方便地切换不同的日志实现(如 Log4j、Logback 等),只要这些日志实现遵循了日志框架定义的 SPI 接口。
- API
- 重点在于让外部开发者能够使用软件系统的功能。它是一种向外暴露功能的方式,方便其他软件组件与之交互。例如,社交媒体平台提供的 API,允许第三方开发者开发与之集成的工具,如在其他应用中分享内容到社交媒体平台、获取用户的社交关系等。
- SPI
- 调用方式
- SPI
- 一般是由框架来发现和调用服务提供者实现的接口。在 Java 中,通常使用
ServiceLoader
类来加载 SPI 实现。服务提供者会在META - INF/services
目录下创建一个文件,文件名是接口的全限定名,文件内容是实现类的全限定名。框架通过ServiceLoader
读取这个文件来加载服务实现。
- 一般是由框架来发现和调用服务提供者实现的接口。在 Java 中,通常使用
- API
- 是由开发者在自己的代码中主动调用。开发者需要了解 API 的使用方法,包括参数、返回值等,然后在自己的代码中按照 API 的规范进行调用。例如,在使用某个云存储服务的 API 时,开发者需要知道如何上传文件(如需要提供文件路径、存储桶名称等参数),并根据 API 返回的结果(如上传成功或失败的提示)来处理后续逻辑。
- SPI
- 可见性和访问控制
- SPI
- 对于使用框架的开发者来说,SPI 的具体实现细节通常是透明的。开发者只需要知道有这个 SPI 接口存在,并且可以根据框架的文档了解如何配置和使用不同的服务提供者。SPI 的实现主要是由服务提供者和框架内部来处理。
- API
- API 的使用方法和接口细节通常是对外公开的。开发者需要详细了解 API 的功能、参数、返回值、错误处理等信息才能正确地使用。同时,API 的提供者可能会对 API 的使用进行限制,如需要授权才能调用某些高级功能,或者对调用频率进行限制等。
- SPI
四、如何在项目中使用SPI?
1. 定义 SPI 接口
- 明确功能边界:首先要确定 SPI 接口所提供的功能范围。例如,在一个图像处理软件中,如果你想通过 SPI 来支持不同的图像滤镜效果,你需要明确这个接口要包含滤镜应用的基本操作,如
applyFilter()
方法,用于对输入的图像应用特定的滤镜。 - 设计接口方法:接口方法的参数和返回值要设计合理。继续以图像滤镜为例,
applyFilter()
方法可能需要接收一个表示图像的对象(如BufferedImage
)作为参数,返回经过滤镜处理后的图像对象。同时,要考虑方法的异常处理机制,比如如果滤镜加载失败或者应用过程中出现错误,应该抛出什么样的异常。
2. 开发 SPI 接口实现
- 实现 SPI 接口:第三方开发者或者项目中的其他模块根据 SPI 接口的定义来实现具体的服务。例如,对于上述图像处理软件的图像滤镜 SPI 接口,开发者可以实现一个 “模糊滤镜” 的服务。在这个实现类中,
applyFilter()
方法会包含将图像进行模糊处理的具体算法,如使用高斯模糊算法来改变图像像素的值,从而实现模糊效果。 - 遵循 SPI 规范:在实现过程中,要严格遵循 SPI 接口的规范。包括方法的签名、参数的使用、返回值的类型等都要和接口定义保持一致。同时,要考虑接口实现的性能和资源利用效率,避免出现内存泄漏或者过度消耗 CPU 资源等问题。
3. 配置 SPI 服务
- 创建配置文件(Java 为例):在 Java 中,SPI 服务提供者需要在
META - INF/services
目录下创建一个文件,文件名是 SPI 接口的全限定名。例如,如果 SPI 接口是com.example.imageprocessing.FilterSPI
,那么就需要在META - INF/services
目录下创建一个名为com.example.imageprocessing.FilterSPI
的文件。 - 填写配置内容:文件内容是实现 SPI 接口的类的全限定名。如果有多个实现类,可以每行写一个实现类的全限定名。比如,对于上述图像滤镜 SPI 接口,文件内容可能是
com.example.imageprocessing.impl.BlurFilterImpl
(假设这是模糊滤镜的实现类)。
4. 在项目中加载和使用 SPI 服务
- 加载 SPI 服务(Java 为例):在项目的框架代码或者需要使用 SPI 服务的地方,可以使用
ServiceLoader
类来加载 SPI 服务。例如,在图像处理软件的主程序中,想要应用所有可用的图像滤镜,可以使用以下代码来加载滤镜 SPI 服务:
import java.util.ServiceLoader;
import com.example.imageprocessing.FilterSPI;public class ImageProcessor {public void applyAllFilters(BufferedImage image) {ServiceLoader<FilterSPI> serviceLoader = ServiceLoader.load(FilterSPI.class);for (FilterSPI filter : serviceLoader) {BufferedImage filteredImage = filter.applyFilter(image);// 对经过滤镜处理后的图像进行后续操作,如显示或保存等}}
}
- 调用 SPI 服务方法:通过
ServiceLoader
加载 SPI 服务后,可以像使用普通对象一样调用 SPI 接口方法。在上述代码中,遍历ServiceLoader
获取到的FilterSPI
对象,并调用applyFilter()
方法来对图像应用不同的滤镜。在实际项目中,根据 SPI 服务的功能,可以将这些服务集成到业务逻辑中,如在特定的业务场景下调用合适的 SPI 服务来完成任务。