Java基础之SPI机制

更新时间:2023-07-12 19:11:32 阅读: 评论:0

Java基础之SPI机制
SPI 机制,全称为 Service Provider Interface,是⼀种服务发现机制。它通过在 ClassPath 路径下的 META-INF/rvices ⽂件夹查找⽂件,⾃动加载⽂件⾥所定义的类。这⼀机制为很多框架扩展提供了可能,⽐如在 Dubbo、JDBC 中都使⽤到了 SPI 机制。本⽂介绍了Java SPI 机制以及在模块化和⾮模块话项⽬中的实现⽅式(此处的模块化指 Java9 引⼊的模块化)
SPI 机制介绍
SPI 全称 Service Provider Interface,是 Java 提供的⼀套⽤来被第三⽅实现或者扩展的接⼝,它可以⽤来启⽤框架扩展和替换组件。SPI 的作⽤就是为这些被扩展的 API 寻找服务实现。
SPI 和 API 的区别
API (Application Programming Interface)在⼤多数情况下,都是实现⽅制定接⼝并完成对接⼝的实现,调⽤⽅仅仅依赖接⼝调⽤,且⽆权选择不同实现。 从使⽤⼈员上来说,API 直接被应⽤开发⼈员使⽤。如下图所⽰,其中模块 A 为接⼝制定⽅和实现⽅,⽽模块 B 为接⼝的使⽤放。
维基百科关于 API 的描述:In building applications, an API (application programming interface) simplifies programming by abstracting the underlying implementation and only exposing objects or actions the developer needs. While a
graphical interface for an email client might provide a ur with a button that performs all the steps for fetching and highlighting new emails, an API for file input/output might give the developer a function that copies a file from one location to another without requiring that the developer understand the file system operations occurring behind the scenes.
SPI (Service Provider Interface)是调⽤⽅来制定接⼝规范,提供给外部来实现,调⽤⽅在调⽤时则选择⾃⼰需要的外部实现,可⽤于启⽤框架扩展和可替换组件。 从使⽤⼈员上来说,SPI 被框架扩展⼈员使⽤。如下图所⽰,A 模块是接⼝的定义⽅和使⽤⽅,⽽ B 模块则是接⼝实现⽅。
SPI 维基百科定义:Service Provider Interface (SPI) is an API intended to be implemented or extended by a third party. It can be ud to enable framework extension and replaceable components
SPI java 官⽅定义:A rvice is a well-known t of interfaces and (usually abstract) class. A rvice provider(SPI) is a specific implementation of a rvice. The class in a provider typically implement the interfaces and subclass the class defined in the rvice itlf. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the application’s class path or by some other platform-specific means.
SPI 的优点
使⽤ Java SPI 机制的优势是实现解耦,使得接⼝的定义与具体业务实现分离,⽽不是耦合在⼀起。应⽤进程可以根据实际业务情况启⽤或替换具体组件。以 java 中的 JDBC 数据库驱动为例,java 官⽅在核⼼库制定了 java.sql.Driver 数据库驱动接⼝,使⽤该接⼝实现了数据库链接等逻辑,但是并没有具体实现数据库驱动接⼝,⽽是交给 MySql 等⼚商去实现具体的数据库接⼝。
不忘沟壑
引⽤⾃:Java SPI 主要是应⽤于⼚商⾃定义组件或插件中。在 java.util.ServiceLoader 的⽂档⾥有⽐
较详细的介绍。简单的总结下java SPI 机制的思想:我们系统⾥抽象的各个模块,往往有很多不同的实现⽅案,⽐如⽇志模块、xml 解析模块、jdbc 模块等⽅案。
⾯向的对象的设计⾥,我们⼀般推荐模块之间基于接⼝编程,模块之间不对实现类进⾏硬编码。⼀旦代码⾥涉及具体的实现类,就违反了可拔插的原则,如果需要替换⼀种实现,就需要修改代码。为了实现在模块装配的时候能不在程序⾥动态指明,这就需要⼀种服务发现机制。 Java SPI 就是提供这样的⼀个机制:为某个接⼝寻找服务实现的机制。有点类似 IOC 的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
SPI 的约定
服务提供⽅需要通过⼀些约定告诉系统⾃⼰所提供的服务的位置,java9 之后⼀共有两种约定⽅式:
1. 通过在 META-INF/rvices/ ⽬录下配置相关⽂件实现。
2. 通过 java9 jigsaw 的导出语句指定服务位置。
A rvice provider is a single type, usually a concrete class. An interface or abstract class is permitted becau itsmile意思
may declare a static provider method, discusd later. The type must be public and must not be an inner class.
A rvice provider and its supporting code may be developed in a module, which is then deployed on the亚太地区是什么意思
application module path or in a modular image. Alternatively, a rvice provider and its supporting code may be
packaged as a JAR file and deployed on the application class path. The advantage of developing a rvice provider in a module is that the provider can be fully encapsulated to hide all details of its implementation.
An application that obtains a rvice loader for a given rvice is indifferent to whether providers of the rvice
are deployed in modules or packaged as JAR files. The application instantiates rvice providers via the rvice
loader’s iterator, or via Provider objects in the rvice loader’s stream, without knowledge of the rv
ice
providers’ locations.meh
模块化语句约定
模块化语句约定适⽤于项⽬已经模块化的情况,以 java.sql.Driver 为例,在模块化⽂件 module-info.java 中添加如下语句,就可以向应⽤提供指定的服务:
provides java.sql.Driver with com.wangzemin.learning.provider.TestDriverProvider;
下⽂以⼀个⾃定义的 java.sql.Driver 服务提供者(也可以⾃⼰随意再另外⼀个模块定义⼀个接⼝)为例,展⽰ SPI 在 java 模块化情况下约定的使⽤⽅式。
1. ⽰例项⽬⽬录结构:
本⽂提供了⼀个完整的例⼦⽤于测试主要包含三个⽂件,TestDriverProvider(⾃定义的 Driver 服务提供者),module-info.java(java 模块化⽂件),Main(⽤于调试以及输出结果)英语三级
2. ⽰例⽂件内容
TestDriverProvider.java
duedatepublic class TestDriverProvider implements Driver {
// Override 的⽅法都为空。
高考1977影评}
模块化⽂件 module-info.java:
module{
us Driver;
requires;
// 指定⾃定义的TestDriverProvider为Driver服务的提供者
provides Driver with TestDriverProvider;
}
主函数(ServiceLoader ⽤于查找合适的服务提供者,下⽂会详细介绍):
栗子的英文
在线英语广播public class Main {
public static void main(String[] args){
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
for(Driver item : loader){
System.out.println("Get class:"+ Class().descriptorString());
}
}
}
3. ⽰例的输出
Get class:Lcom/wangzemin/learning/provider/TestDriverProvider;
由输出可以看到,ServiceLoader 可以成功定位到 TestDriverProvider。
模块化约定:
A rvice provider that is developed in a module must be specified in a provides directive in the module declaration.
The provides directive specifies both the rvice and the rvice provider; this helps to locate the provider when another module, with a us directive for the rvice, obtains a rvice loader for the rvice. It is strongly
recommended that the module does not export the package containing the rvice provider. There is no support for a module specifying, in a provides directive, a rvice provider in another module.
A rvice provider that is developed in a module has no control over when it is instantiated, since that occurs at the
behest of the application, but it does have control over how it is instantiated:
If the rvice provider declares a provider method, then the rvice loader invokes that method to obtain an instance of the rvice provider. A provider method is a public static method named “provider” with no formal parameters and a return type that is assignable to the rvice’s interface or class.
In this ca, the rvice provider itlf need not be assignable to the rvice’s interface or class.
If the rvice provider does not declare a provider method, then the rvice provider is instantiated directly, via its provider constructor. A provider constructor is a public constructor with no formal parameters.
In this ca, the rvice provider must be assignable to the rvice’s interface or class
A rvice provider that is deployed as an automatic module on the application module path must have a provider
constructor. There is no support for a provider method in this ca.
As an example, suppo a module specifies the following directives:
ample.CodecFactory ample.impl.StandardCodecs;
ample.CodecFactory ample.impl.ExtendedCodecsFactory;
where
A rvice loader will instantiate StandardCodecs via its constructor, and will instantiate ExtendedCodecsFactory by invoking its provider method. The requirement that the provider constructor or provider method is public helps to document the intent that the class (that is, the rvice provider) will be instantiated by an entity (that is, a rvice loader) which is outside the class’s package.
配置⽂件约定
配置⽂件约定适⽤于项⽬没有模块化的情况,需要在 classpath 下的 META-INF/rvices/ ⽬录⾥创建⼀个以服务接⼝命名的⽂档,这个⽂档⾥的内容就是这个接⼝的具体的实现类。
下⽂同样以⼀个⾃定义的 java.sql.Driver 服务提供者(也可以⾃⼰随意再另外⼀个模块定义⼀个接⼝)为例,展⽰ SPI 在 java 配置⽂件约定下的使⽤⽅式。
1. ⽰例项⽬⽬录结构:
本⽂提供了⼀个完整的例⼦⽤于测试主要包含三个⽂件,TestDriverProvider(⾃定义的 Driver 服务提供者,和上⽂⼀致),Main(⽤于调试以及输出结果,和上⽂⼀致),META-INF/rvices/java.sql.Driver ⽂件(⽤于指定服务提供着的位置)
2. ⽰例⽂件内容
TestDriverProvider 和 Main 与上⽂中均⼀致,不再次详述,此处仅仅展⽰ META-INF/rvices/java.sql.Driver ⽂件的内容,该⽂件只包含⼀⾏内容:
com.wangzemin.learning.learning.provider.TestDriverProvider
3. ⽰例的输出
Get class:Lcom/wangzemin/learning/provider/TestDriverProvider;
由输出可以看到,ServiceLoader 可以成功定位到 TestDriverProvider。
配置⽂件约定
A rvice provider that is packaged as a JAR file for the class path is identified by placing a provider-configuration file
in the resource directory META-INF/rvices. The name of the provider-configuration file is the fully qualified binary name of the rvice. The provider-configuration file contains a list of fully qualified binary names of rvice providers, one per line.
For example, suppo the rvice ample.impl.StandardCodecs is packaged in a JAR file for the class path. The JAR file will contain a provider-configuration file named:
META-INF/ample.CodecFactory
that contains the line:
The provider-configuration file must be encoded in UTF-8. Space and tab characters surrounding each rvice
provider’s name, as well as blank lines, are ignored. The comment character is ‘#’ (’\u0023’ NUMBE
R SIGN); on each line all characters following the first comment character are ignored. If a rvice provider class name is listed more than once in a provider-configuration file then the duplicate is ignored. If a rvice provider class is named in more than one configuration file then the duplicate is ignored.
A rvice provider that is mentioned in a provider-configuration file may be located in the same JAR file as the
provider-configuration file or in a different JAR file. The rvice provider must be visible from the class loader that is initially queried to locate the provider-configuration file; this is not necessarily the class loader which ultimately locates the provider-configuration file.
SPI 原理
上⽂讲述了 SPI 的⼀些约定,那么有了这些约定之后,SPI 机制是如何定位到对应的服务提供者的类并进⾏加载的呢?SPI 服务的加载可以分为两部分:
1. 类全称限定名的获取,即知道哪些类是服务提供者。
2. 类加载,把获取到的类加载到内存中,涉及上下⽂类加载器。
类限定名获取
模块化情况下
skillsoft可以参考,jigsaw 模块化语法本⾝就⽀持 SPI 服务,通过 provide xxxx with yyyy,可以为 xxxx 服务指定⼀个服务提供者 yyyy,这个解析过程由 jigsaw 实现。

本文发布于:2023-07-12 19:11:32,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/90/175364.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:实现   服务   机制   模块   模块化   需要   约定   提供
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图