前言
本文是做Dubbo的SPI机制的铺垫文章,那么本文我们就来看看JAVA的SPI是个啥!SPI全称为 (Service Provider Interface),通常在一些第三方框架上有的!本文先使用数据库驱动来讲解一下!市面上有许许多多的数据库,如MySQL、SQLServer、Oracle等等,这些在JAVA中都能对接,是应为有Driver接口在顶层抽象控制!
Driver提供一套接口规范,那么各第三方数据库驱动依赖需要实现Driver接口,然后各个数据库驱动依赖包中配置实现类全限定名,如下!
那么这个文件需要放到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方法!
代码目录
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文件
执行结果
源码分析
流程梳理
通过接口,得到接口信息,然后到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的,连调方法如下
先找当前线程绑定的 ClassLoader
构建ServiceLoader
赋值service、loader、acc
lookupIterator = new LazyIterator(service, loader);
实例化一个迭代器!并且将需要加载的接口和类加载器通过构造方法初始化给LazyIterator!
注意LazyIterator是ServiceLoader的内部类!
Iterator iterator= loginServices.iterator();
loginServices.iterator()为什么能点出来呢!因为ServiceLoader中有一个获取迭代器的方法!
但是这里创建出来的迭代器中的hasNext、next方法变相的都调用着ServiceLoader中内部类LazyIterator中的hasNext和next方法。那么这里就是返回一个迭代器对象。
iterator.hasNext()
这个hashNext会调用lookupIterator.hasNext();
进入hasNextService()方法
拼接接口全路径为META-INF/services/com.javaspi.service.LoginService
pending = parse(service, configs.nextElement());
parse主要就是解析META-INF/services/com.javaspi.service.LoginService文件中的内容,并且返回Iterator
iterator.next();
iterator.next();方法调用的是lookupIterator.next();方法!
这里就是通过反射并实例化实现类,并且将实现类返回!
结束语
那么到这里JAVA的SPI机制就分析完成了,后续有时间会在分析Dubbo的SPI机制!