mysql spi_深入理解 Java 中 SPI 机制

一、简介

SPI(Service Provider Interface),是JDK内置的一种

服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻zhao服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是

解耦。

SPI与API区别:

API是调用并用于实现目标的类、接口、方法等的描述;

SPI是扩展和实现以实现目标的类、接口、方法等的描述;

换句话说,API 为操作提供特定的类、方法,SPI 通过操作来符合特定的类、方法。

SPI整体机制图如下:

bb5b957505aed5cd9f5704b16862e38d.png

当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查zhao服务的实现的工具类是:java.util.ServiceLoader。

二、应用场景

SPI扩展机制应用场景有很多,比如Common-Logging,JDBC,Dubbo等等。

SPI流程:

有关组织和公式定义接口标准

第三方提供具体实现: 实现具体方法, 配置 META-INF/services/${interface_name} 文件

开发者使用

比如JDBC场景下:

首先在Java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商提供。

在MySQL的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。

同样在PostgreSQL的jar包PostgreSQL-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是PostgreSQL对Java的java.sql.Driver的实现。

三、使用demo

1.定义一个接口HelloSPI。

package com.vivo.study.spidemo.spi;public interface HelloSPI {

void sayHello();

}

2.完成接口的多个实现。

package com.vivo.study.spidemo.spi.impl;import com.vivo.study.spidemo.spi.HelloSPI;public class ImageHello implements HelloSPI {

public void sayHello() {

System.out.println("Image Hello");

}

}

package com.vivo.study.spidemo.spi.impl;import com.vivo.study.spidemo.spi.HelloSPI;public class TextHello implements HelloSPI {

public void sayHello() {

System.out.println("Text Hello");

}

}

在META-INF/services/目录里创建一个以com.vivo.study.spidemo.spi.HelloSPI的文件,这个文件里的内容就是这个接口的具体的实现类。

568d4a12b51802d24f76c2866cfda683.png

具体内容如下:

com.vivo.study.spidemo.spi.impl.ImageHello

com.vivo.study.spidemo.spi.impl.TextHello

3.使用 ServiceLoader 来加载配置文件中指定的实现

package com.vivo.study.spidemo.testimport java.util.ServiceLoader;import com.vivo.study.spidemo.spi.HelloSPI;public class SPIDemo {

public static void main(String[] args) {

ServiceLoader serviceLoader = ServiceLoader.load(HelloSPI.class);        // 执行不同厂商的业务实现,具体根据业务需求配置

for (HelloSPI helloSPI : serviceLoader) {

helloSPI.sayHello();

}

}

}

输出结果如下:

Image Hello

Text Hello

四、源码分析

// ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者

public final class ServiceLoader implements Iterable

{

// 查找配置文件的目录

private static final String PREFIX = "META-INF/services/";

// 表示要被加载的服务的类或接口

private final Class service;

// 这个ClassLoader用来定位,加载,实例化服务提供者

private final ClassLoader loader;

// 访问控制上下文

private final AccessControlContext acc;

// 缓存已经被实例化的服务提供者,按照实例化的顺序存储

private LinkedHashMap providers = new LinkedHashMap<>();

// 迭代器

private LazyIterator lookupIterator;

}

// 服务提供者查找的迭代器public Iterator iterator() {    return new Iterator() {

Iterator> knownProviders

= providers.entrySet().iterator();        // hasNext方法

public boolean hasNext() {            if (knownProviders.hasNext())                return true;            return lookupIterator.hasNext();

}        // next方法

public S next() {            if (knownProviders.hasNext())                return knownProviders.next().getValue();            return lookupIterator.next();

}

};

}

// 服务提供者查找的迭代器

private class LazyIterator implements Iterator {

// 服务提供者接口

Class service;

// 类加载器

ClassLoader loader;

// 保存实现类的url

Enumeration configs = null;

// 保存实现类的全名

Iterator pending = null;

// 迭代器中下一个实现类的全名

String nextName = null;

public boolean hasNext() {

if (nextName != null) {

return true;

}

if (configs == null) {

try {

String fullName = PREFIX + service.getName();

if (loader == null)

configs = ClassLoader.getSystemResources(fullName);

else

configs = loader.getResources(fullName);

} catch (IOException x) {

fail(service, "Error locating configuration files", x);

}

}

while ((pending == null) || !pending.hasNext()) {

if (!configs.hasMoreElements()) {

return false;

}

pending = parse(service, configs.nextElement());

}

nextName = pending.next();

return true;

}

public S next() {

if (!hasNext()) {

throw new NoSuchElementException();

}

String cn = nextName;

nextName = null;

Class> c = null;

try {

c = Class.forName(cn, false, loader);

} catch (ClassNotFoundException x) {

fail(service,"Provider " + cn + " not found");

}

if (!service.isAssignableFrom(c)) {

fail(service, "Provider " + cn  + " not a subtype");

}

try {

S p = service.cast(c.newInstance());

providers.put(cn, p);

return p;

} catch (Throwable x) {

fail(service, "Provider " + cn + " could not be instantiated: " + x, x);

}

throw new Error();          // This cannot happen

}

}

首先,ServiceLoader实现了Iterable接口,所以它有迭代器的属性,这里主要都是实现了迭代器的hasNext和next方法。这里主要都是调用的lookupIterator的相应hasNext和next方法,lookupIterator是懒加载迭代器。

其次,LazyIterator中的hasNext方法,静态变量PREFIX就是”META-INF/services/”目录,这也就是为什么需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件。

最后,通过反射方法Class.forName()加载类对象,并用newInstance方法将类实例化,并把实例化后的类缓存到providers对象中,(LinkedHashMap类型) 然后返回实例对象。

五、不足

1.不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。

2.获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。

3.多个并发多线程使用 ServiceLoader 类的实例是不安全的。

六、规避

针对以上的不足点,我们在SPI机制选择时,可以考虑使用dubbo实现的SPI机制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值