一点一点实现一个RPC框架二学习javaspi

编程

前言

深入dubbo源码前最好先了解下java spi(service provider interface)机制, 简单来说, spi可以帮我们加载指定文件中描述的接口实现类. 嗯...就这? 是不是太简单了, 虽然我是个菜瓜, 那我也知道Class.forName呀~ 那我们来研究下~

java spi

demo

尽管千篇一律, 还是给出一个可运行demo

// 首先你需要一个对外接口

public interface GreetOrBye {

String say(String name);

}

// 以及两个实现类

public class Bye implements GreetOrBye {

@Override

public String say(String name) {

return "bye " + name;

}

}

public class Greet implements GreetOrBye {

@Override

public String say(String name) {

return "hi " + name;

}

}

//然后是执行类

public class Launcher {

public static void say(String name) {

ServiceLoader<GreetOrBye> greetOrByeServiceLoader = ServiceLoader.load(GreetOrBye.class);

Iterator<GreetOrBye> iterator = greetOrByeServiceLoader.iterator();

while (iterator.hasNext()) {

GreetOrBye greetOrBye = iterator.next();

System.out.println(greetOrBye.say(name));

}

}

public static void main(String[] args) {

say("wahahah");

}

}

下面是需要指定的文件, 目录名称固定META-INF下的services, 文件名称为接口全限定名

文件内容是实现类的全限定名

运行结果

hi wahahah

bye wahahah

平平无奇~(古天乐?)

虽然还没有看ServiceLoader的代码, 但是以我40年代码经验来看, 里面必定就是一个读文件, 反射创建对象的过程. 那么好吧~ 接下来证明自己

ServiceLoader

菜瓜惯例, 先看注释, 再看属性.

注释总结

注释中先定义了两个名词

service 指暴露的接口或者类, 通常是抽象类, 也可以是具体类, 但是不建议.

service provider 指实现类, 通常是一个代理类, 内容决定具体的实现类

然后提了几点要求

**service provider 必须提供无参构造方法 **

service provider 在META-INF/services中定义, 文件名称是service的全限定名称, 比如com.togo.spi.helloworld.GreetOrBye, 文件内容是每一行都是service provider的全限定名称, 比如com.togo.spi.helloworld.impl.Greet

如果一个service provider出现在多个文件或者在一个文件中出现多次, service loader会去重.

service provider和配置文件不一定要在一个jar中, 但是service provider必须可以被加载配置文件的loader访问到(这个我们到时候关注下)

service provider都是懒加载(按需加载)

ServiceLoader是线程不安全的

属性

ServiceLoader实现了Iterable接口, 属性如下.

// 文件路径

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

// 暴露的接口类型

private final Class<S> service;

// 类加载器

private final ClassLoader loader;

// 安全相关

private final AccessControlContext acc;

// 缓存加载的类

private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 懒加载迭代器

private LazyIterator lookupIterator;

看名字和注释大家应该能猜出各个属性的作用, 我们通过源码来进一步了解下.

源码

public static <S> ServiceLoader<S> load(Class<S> service) {

ClassLoader cl = Thread.currentThread().getContextClassLoader();

return ServiceLoader.load(service, cl);

}

public static <S> ServiceLoader<S> load(Class<S> service,

ClassLoader loader){

return new ServiceLoader<>(service, loader);

}

private ServiceLoader(Class<S> svc, ClassLoader cl) {

service = Objects.requireNonNull(svc, "Service interface cannot be null");

loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;

acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;

reload();

}

public void reload() {

providers.clear();

lookupIterator = new LazyIterator(service, loader);

}

private LazyIterator(Class<S> service, ClassLoader loader) {

this.service = service;

this.loader = loader;

}

load方法其实就是创建一个新的ServiceLoader对象(因为构造方法私有~), 使用的就是当前线程的类加载器, 整个构造过程就是一些赋值操作. 在reload方法中会清除map中缓存的对象, 并重新创建一个LazyIterator, LazyIterator构造方法中就只是赋值了. 既然说是懒加载了, 那么重要的操作当然都在使用的时候了.

public Iterator<S> iterator() {

return new Iterator<S>() {

// 已经加载的对象

Iterator<Map.Entry<String,S>> knownProviders

= providers.entrySet().iterator();

// 先从已经加载的对象中找, 没有再从lookupIterator中找

public boolean hasNext() {

if (knownProviders.hasNext())

return true;

return lookupIterator.hasNext();

}

// 逻辑同上

public S next() {

if (knownProviders.hasNext())

return knownProviders.next().getValue();

return lookupIterator.next();

}

public void remove() {

throw new UnsupportedOperationException();

}

};

}

public boolean hasNext() {

if (acc == null) { // 默认走这里, AccessControlContext作者没关注~~

return hasNextService();

} else {

PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {

public Boolean run() { return hasNextService(); }

};

return AccessController.doPrivileged(action, acc);

}

}

private boolean hasNextService() {

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;

}

// 解析文件, 并将文件中的字符串存储到list中, 返回该list的iterator

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

}

nextName = pending.next();

return true;

}

next()方法也比较简单, 就是获取到类全限定名称后做了反射创建对象的操作, 跟我们开始的预测是一样.

总结

至此java spi分析到这, 代码很简单, 重点还是学习思想-面向接口编程, 比如我们经常使用不同的数据库驱动代码, 在DriverManager中就有ServiceLoader的身影. 下一篇研究下dubbo增强版spi~

以上是 一点一点实现一个RPC框架二学习javaspi 的全部内容, 来源链接: utcz.com/z/517453.html

回到顶部