Motan 是微博技术团队研发的基于 Java 的轻量级 RPC 框架,已在微博内部大规模应用多年,每天稳定支撑微博上亿次的内部调用。Motan 基于微博的高并发和高负载场景优化,成为一套简单、易用、高可用的 RPC 服务框架。Motan框架中主要有register、transport、serialize、protocol几个功能模块,各个功能模块都支持通过SPI进行扩展,下面我们就来看看Motan中SPI是如何实现的。
什么是SPI
SPI(Service Provider Interface)服务提供接口,是指的一种服务发现机制,为某个特定的接口寻找服务的实现。在大规模的软件开发中经常采用这样的机制,实现模块之间基于接口编程,隐藏其实现细节,不同的服务提供商进行扩展实现,最终实现无需修改代码即可调用,实现完全无代码入侵。
JDK自带的SPI实例
JDK6以后提供了java.util.ServiceLoader类来实现从配置文件中加载子类或者接口的实现类。
从JDK API文档中我们可以知道实现起来相当简单,只需四步:
1.定义接口
package test;
public interface HelloService {
public String sayHello();
}
2.写2个实现类,必须是有无构造函数的类
package test;
public class HelloServiceImpl1 implements HelloService {
@Override
public String sayHello() {
return "say hello from HelloServiceImpl1";
}
}
package test;
public class HelloServiceImpl2 implements HelloService {
@Override
public String sayHello() {
return "say hello from HelloServiceImpl2";
}
}
3.在资源目录 META-INF/services 中放置提供者配置文件,文件名为文件名称为接口类的完整包路径和类名test.HelloService,
文件必须使用 UTF-8 编码,内容如下:
test.HelloServiceImpl1
test.HelloServiceImpl2
4.测试调用
package test;
import java.util.ServiceLoader;
public class ServiceLoaderTest {
public static void main(String[] args) {
ServiceLoader<HelloService> serviceLoader = ServiceLoader.load(HelloService.class);
for (HelloService service : serviceLoader) {
System.out.println(service.sayHello());
}
}
}
执行后打印结果:
say hello from HelloServiceImpl1
say hello from HelloServiceImpl2
JDK自带的ServiceLoader功能比较弱,只提供了如下几个方法:
1.reload:清除服务实例缓存,重新加载;
2.iterator:延迟方式加载服务,按配置的顺序生成服务实例;
3.load:从上下文加载器(ContextClassLoader)加载。
4.loadInstalled,系统加载器(SystemClassLoader)优先,没有则从上下文加载器加载。
实际上,JDK自带的ServiceLoader并不能满足开发的需求,例如单例服务,按别名使用指定的服务实现,优先级控制等等。
Motan的SPI调用实例
Motan底层模块之间调用也是采用SPI模式(com.weibo.api.motan.core.extension.ExtensionLoader),可以根据配置实现调用不同的服务实例。
Motan提供了三个注解:
@Spi 声明接口为SPI服务接口;
@Scope 声明单例模式还是多例模式
@Activation 声明服务优先级(sequence值越小越优先调用)、分组查询、以及是否支持重试调用。
@SpiMeta设定服务别名,没配置则用实现类的类名为别名
ExtensionLoader兼容JDK自带的ServiceLoader,也就是说跟ServiceLoader的配置方式是一样的,在META-INF/services创建服务接口文件,内容为实现类的完整包路径列表。
改写上面的四个步骤:
1.服务接口类
package test;
import com.weibo.api.motan.core.extension.Scope;
import com.weibo.api.motan.core.extension.Spi;
@Spi(scope=Scope.PROTOTYPE) //注解为SPI服务,指定实例方式
public interface HelloService {
public String sayHello();
}
2.两个实现类
package test;
import com.weibo.api.motan.core.extension.Activation;
import com.weibo.api.motan.core.extension.SpiMeta;
@SpiMeta(name="hello1")
@Activation(sequence=1,key={"hello"})
public class HelloServiceImpl1 implements HelloService {
@Override
public String sayHello() {
return "say hello from HelloServiceImpl1";
}
}
package test;
import com.weibo.api.motan.core.extension.Activation;
import com.weibo.api.motan.core.extension.SpiMeta;
@SpiMeta(name="hello2")
@Activation(sequence=2,key={"hello"})
public class HelloServiceImpl2 implements HelloService {
@Override
public String sayHello() {
return "say hello from HelloServiceImpl2";
}
}
3.Motan模式兼容JDK的ServiceLoader,配置是一样的。
4.调用测试
package test;
import java.util.List;
import com.weibo.api.motan.core.extension.ExtensionLoader;
public class ServiceLoaderTest {
public static void main(String[] args) {
HelloService service = ExtensionLoader.getExtensionLoader(HelloService.class).getExtension("hello1");//获取指定hello1服务实例
System.out.println(service.sayHello());
service = ExtensionLoader.getExtensionLoader(HelloService.class).getExtension("hello2");//获取指定hello2服务实例
System.out.println(service.sayHello());
List<HelloService> services = ExtensionLoader.getExtensionLoader(HelloService.class).getExtensions("hello");//对应key分组按sequence优先级排序,为空则所有的
for (HelloService svc : services) {
System.out.println(svc.sayHello());
}
}
}
执行结果:
say hello from HelloServiceImpl1
say hello from HelloServiceImpl2
say hello from HelloServiceImpl1
say hello from HelloServiceImpl2
Motan提供的SPI模式可以实现:
1.单例模式下只会构造一次实例
2.多例模式下在每次获取的时候进行实例化
3.支持手动添加实现类
4.支持ServiceLoader一样的获取所有的实现实例
Motan的SPI实现代码解析
motan通过私有静态成员变量ConcurrentMap<Class<?>, ExtensionLoader<?>> extensionLoaders 缓存所有类加载器。
在调用getExtensionLoader(HelloService.class)先检查是否有缓存接口服务类的类加载器,没有则创建一个类加载器,并以以接口类为key放入到extensionLoaders缓存起来,然后开始执行初始化工作,
从META-INF/services目录下找该接口服务类的配置文件,并将配置内容里面的服务实现类读取出来缓存到extensionClasses类列表中以@SpiMeta的配置的name为key(未配置则以实现类名为key),此时并没有创建类的实例,采用的是懒加载模式。
getExtension("hello1")时先判断是否是单例模式,如是则判断singletonInstances的中是否有该实例,没有则实例化后缓存起来,并返回;
如果不是单例模式,则从extensionClasses根据spiMeta获取到实现类创建实例返回。