前言

本文是做Dubbo的SPI机制的铺垫文章,那么本文我们就来看看JAVA的SPI是个啥!SPI全称为 (Service Provider Interface),通常在一些第三方框架上有的!本文先使用数据库驱动来讲解一下!市面上有许许多多的数据库,如MySQL、SQLServer、Oracle等等,这些在JAVA中都能对接,是应为有Driver接口在顶层抽象控制!

JAVA-SPI机制_sql


Driver提供一套接口规范,那么各第三方数据库驱动依赖需要实现Driver接口,然后各个数据库驱动依赖包中配置实现类全限定名,如下!

JAVA-SPI机制_java_02


那么这个文件需要放到META-INF.services目录下面,文件名就是Driver接口的全限定名!当程序需要Driver的实现类的时候就可以通过META-INF.services下找到java.sql.Driver,然后通过java.sql.Driver文件中的实现类路径获取实现类!

说白了顶层通过面向Driver接口编程,但是并不知道具体使用那种类型的数据库,那么我们数据库启动包就可以使用JAVA的SPI机制告诉Driver使用那个实现类!用户导入MySQL驱动包,那么就使用com.mysql.cj.jdbc.Driver作为实现类,如果是SQLServer那么就使用SQLServer的!就是这么个意思!

代码编写

场景铺垫
登录现在方式挺多的,我们现在有微信登录、QQ登录、手机短信登录,那么我们在顶层创建一个LoginService接口,LoginService接口中有一个login方法,然后有对应的微信、QQ、手机短信实现类,继承LoginService,实现login方法!

代码目录

JAVA-SPI机制_数据库_03


LoginService

public interface LoginService {

public String login();

}

QqLoginServiceImpl

public class QqLoginServiceImpl implements LoginService {
@Override
public String login() {
return "QQ登录";
}
}

SmsLoginServiceImpl

public class SmsLoginServiceImpl implements LoginService {
@Override
public String login() {
return "sms登录";
}
}

WxLoginServiceImpl

public class WxLoginServiceImpl implements LoginService {
@Override
public String login() {
return "WX登录";
}
}

调用测试

public class JavaSpiApplication {

public static void main(String[] args) {
ServiceLoader<LoginService> loginServices = ServiceLoader.load(LoginService.class);
for (LoginService lo : loginServices) {
System.out.println(lo.login());
}
}
}

METE-INF下创建service目录,添加com.javaspi.service.LoginService文件

JAVA-SPI机制_sql_04

执行结果

JAVA-SPI机制_数据库_05

源码分析

流程梳理
通过接口,得到接口信息,然后到META-INF/service下匹配对应的文件,然后得到文件中的实现类全限定名通过反射创建实现类!并存储到迭代器中!

修改代码
替换原有的for为where迭代!让代码更加直观!

public static void main(String[] args) {
ServiceLoader<LoginService> loginServices = ServiceLoader.load(LoginService.class);
Iterator<LoginService> iterator= loginServices.iterator();
while (iterator.hasNext()){
LoginService next = iterator.next();
System.out.println(next.login());
}
}

ServiceLoader.load(LoginService.class)

ServiceLoader<LoginService> loginServices = ServiceLoader.load(LoginService.class);

这行代码执行就是通过LoginService的信息去构建一个ServiceLoader的,连调方法如下

JAVA-SPI机制_数据库_06


先找当前线程绑定的 ClassLoader

JAVA-SPI机制_java_07


构建ServiceLoader

JAVA-SPI机制_数据库_08


赋值service、loader、acc

JAVA-SPI机制_java_09

lookupIterator = new LazyIterator(service, loader);

实例化一个迭代器!并且将需要加载的接口和类加载器通过构造方法初始化给LazyIterator!

JAVA-SPI机制_sql_10


注意LazyIterator是ServiceLoader的内部类!Iterator iterator= loginServices.iterator();

loginServices.iterator()为什么能点出来呢!因为ServiceLoader中有一个获取迭代器的方法!

JAVA-SPI机制_sql_11


但是这里创建出来的迭代器中的hasNext、next方法变相的都调用着ServiceLoader中内部类LazyIterator中的hasNext和next方法。那么这里就是返回一个迭代器对象。iterator.hasNext()

这个hashNext会调用lookupIterator.hasNext();

JAVA-SPI机制_sql_12


进入hasNextService()方法

JAVA-SPI机制_数据库_13


拼接接口全路径为META-INF/services/com.javaspi.service.LoginService

JAVA-SPI机制_sql_14

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

parse主要就是解析META-INF/services/com.javaspi.service.LoginService文件中的内容,并且返回Iterator

JAVA-SPI机制_java_15

iterator.next();

iterator.next();方法调用的是lookupIterator.next();方法!

JAVA-SPI机制_sql_16


这里就是通过反射并实例化实现类,并且将实现类返回!

结束语
那么到这里JAVA的SPI机制就分析完成了,后续有时间会在分析Dubbo的SPI机制!