目录
- 1. Java SPI介绍
- 2. Java SPI的运行流程
- 3. Java SPI在JDBC中的应用
- 4. Java SPI的三大规范要素
- 5. 自己实现一个SPI应用
- 5.1 Service接口
- 5.2 运营商1的Service Provider
- 5.3 运营商2的Service Provider
- 5.3 手机使用网络
1. Java SPI介绍
SPI(Service Provider Interface)是一种基于ClassLoader来发现并加载服务的机制。用于实现各种插件或灵活替换框架所使用的组件。设计思想采用:面向接口 + 配置文件 + 反射技术
一个标准的SPI,由以下3部分构成:
- Service:一个公开的接口或抽象类,定义了一个抽象的功能模块
- Service Provider:Service接口的一个实现类
- ServiceLoader:是SPI机制中的核心组件,负责在运行时发现并加载Service Provider
优点:基于面向接口编程,实现了模块之间的解耦
应用场景:JDBC、slf4j、Servlet容器初始化等
2. Java SPI的运行流程
3. Java SPI在JDBC中的应用
JDBC(Java DataBase Connectivity)是Java语言来访问数据库的一套API,每个数据库厂商都会提供各自的JDBC实现
以前我们使用JDBC访问Mysql数据库的时候,都会执行Class.forName("com.mysql.cj.jdbc.Driver")
,这样就会实例化驱动类,同时也会执行驱动类中的static代码块中的DriverManager.registerDriver(new Driver())
,将驱动类注册到Drivermanager中
但是将驱动类的全路径名称写到代码中,非常不方便。如果将类的全路径名写到配置文件中,虽然方便,但是还需要我们去记驱动类的全路径名
使用Java的SPI就可以解决这个问题。让Mysql驱动提供方同时提供驱动包和驱动配置文件,Java SPI通过类ClassLoader(通过getResource/getResources从指定位置读取classpath中的配置文件、可以加载类)自动从指定位置读取配置文件并进行驱动类的加载。这样就不用我们进行驱动类的加载了,而且Mysql驱动提供方能很方便的进行驱动的升级
4. Java SPI的三大规范要素
- Service Provider类必须具备无参的默认构造方法
因为会通过反射技术实例化Service Provider,是不带参数的
Mysql的驱动类实现如下:
package com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
- 规范的配置文件
- 配置文件路径:必须在jar包中的META-INF/services目录下
- 配置文件名称:Service接口的全路径名
- 配置文件内容:Service Provider类的全路径名。如果有Service实现类(Service Provider类),那么每一个Service Provider类的全路径名在文件中单独占一行
Mysql驱动的配置文件如下所示:
- 保证能加载到配置文件和Service Provider类
- 方式一:将Service Provider的jar包放到classpath中。该方式最常用,通过maven引入依赖也是这种方式。程序运行时自动加载配置文件和Service Provider类。虽然能加载Service Provider类,但是不能引用Service Provider类,可以参考Mysql的驱动类进行注册获取Service Provider类的引用
- 方式二:将Service Provider的jar包放到jre的扩展目录中。程序运行时自动加载配置文件和Service Provider类。虽然能加载Service Provider类,但是不能引用Service Provider类,可以参考Mysql的驱动类进行注册获取Service Provider类的引用
- 方式三:自定义一个ClassLoader。需要我们自己在程序中实现。程序运行时自动加载配置文件和Service Provider类。这种方式不但能加载Service Provider类,还能获取到Service Provider类
5. 自己实现一个SPI应用
应用背景:手机需要连接网络,定义一个连接网络的Service接口。由运营商1和运营商2实现Service接口来提供具体的网络服务
我们先在IDEA创建一个项目javaSpiTest
5.1 Service接口
在项目下面创建一个module,名称为network。定义一个NetworkService接口,内容如下:
package com.hh.network;
public interface NetworkService {
void connectNetwork();
}
5.2 运营商1的Service Provider
在项目下面创建一个module,名称为operator1,依赖module network。定义一个BeijingNetworkServiceProvider类,内容如下:
package com.hh.operator1;
import com.hh.network.NetworkService;
import java.io.UnsupportedEncodingException;
public class BeijingNetworkServiceProvider implements NetworkService {
@Override
public void connectNetwork() {
try {
System.out.println(
new String("operator1提供的北京网络".getBytes(), "UTF-8")
);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
再定义一个ShanghaiNetworkServiceProvider类,内容如下:
package com.hh.operator1;
import com.hh.network.NetworkService;
import java.io.UnsupportedEncodingException;
public class ShanghaiNetworkServiceProvider implements NetworkService {
@Override
public void connectNetwork() {
try {
System.out.println(
new String("operator1提供的上海网络".getBytes(), "UTF-8")
);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
配置文件:在resources目录下新建文件META-INF/services/com.hh.network.NetworkService,内容如下:
com.hh.operator1.BeijingNetworkServiceProvider
com.hh.operator1.ShanghaiNetworkServiceProvider
5.3 运营商2的Service Provider
在项目下面创建一个module,名称为operator2,依赖module network。定义一个GuangzhouNetworkServiceProvider类,内容如下:
package com.hh.operator2;
import com.hh.network.NetworkService;
import java.io.UnsupportedEncodingException;
public class GuangzhouNetworkServiceProvider implements NetworkService {
@Override
public void connectNetwork() {
try {
System.out.println(
new String("operator2提供的广州网络".getBytes(), "UTF-8")
);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
配置文件:在resources目录下新建文件META-INF/services/com.hh.network.NetworkService,内容如下:
com.hh.operator2.GuangzhouNetworkServiceProvider
5.3 手机使用网络
在项目下面创建一个module,名称为phone,依赖module operator1。定义一个PhoneNetworkUse类,内容如下:
package com.hh.phone;
import com.hh.network.NetworkService;
import java.util.ServiceLoader;
public class PhoneNetworkUse {
public static void main(String[] args) {
ServiceLoader<NetworkService> serviceLoader =
ServiceLoader.load(NetworkService.class);
for(NetworkService networkService: serviceLoader) {
networkService.connectNetwork();
}
}
}
运行程序,结果如下:
operator1提供的北京网络
operator1提供的上海网络
如果将依赖module operator1,改成依赖module operator2。再运行程序,结果如下:
operator2提供的广州网络