(一)什么是SPI机制?Java中的SPI机制是如何实现的?
(1)首先先说一下JavaSPI机制(Service Provider Interface)其实说白了就是定义一个接口,但是可以有多个实现该接口的实现类,其实也是一种服务发现机制。
- 其实SPI机制的本质就是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类
(2)那么Dubbo中的SPI机制和Java中的SPi机制又有什么区别呢?
- 首先Java中的SPI机制是基于接口的编程+策略模式+配置文件的组合方式实现的动态加载机制
- 其实具体的JavaSPI的实现就是:
- 1.你先定义一个A接口,比如是com.wangwei.A这个接口
- 2.然后你需要在src/main/resources/ 下建立 /META-INF/services 目录下定义和A接口相同的文件com.wangwei.A名字,然后需要在这个com.wangwei.A文件中 定义一些你需要实现这个接口的实现类这里有多个实现类的
- 3.然后你在使用的时候需要去遍历这个接口的所有实现类,然后拿到你想要拿到的实现类,然后使用,是这样的一个流程。
那么其实JavaSPI机制的底层实现原理:
其实主要ServiceLoader这个类进行去加载的
public final class ServiceLoader<S>
implements Iterable<S>
{
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
这里中间就直接省略了。。。。。。
private class LazyIterator implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
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;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
JavaSP机制的缺点:
- 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
- 多个并发多线程使用ServiceLoader类的实例是不安全的。
- 扩展如果依赖其他的扩展,做不到自动注入和装配
- 不提供类似于Spring的IOC和AOP功能
- 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持
(二)dubbo的SPI机制底层是如何实现的?
(1)dubbo中的SPI机制
- 其实为什么在dubbo中又重新定义了一套dubbo的SPI机制,就是因为java中的SPI机制的一些缺点,不能满足dubbo的使用场景或者说效率没有那么高效
- 所以dubbo自己封装了一套更强大的SPI机制,直接封装到了ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径,并且可以直接给实现类进行一个命名,然后在使用的时候,直接通过@SPI(“名字”)去找到具体的实现类,比如:random(名字)=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance(实现类)
(2)dubbo中SPI机制的使用流程:
- 首先如果你想给一个接口需要用dubbo的SPI机制实现,那么首先你需要给这个接口上加上@SPI(“使用默认的实现类的名字”)这个注解
- 然后需要在你接口中的方法上加上一个@Adaptive注解,这个注解的核心功能就是:会为该方法生成对应的代码。方法内部会根据方法的参数,来决定使用哪个扩展,@Adaptive注解用在类上代表实现一个装饰类,类似于设计模式中的装饰模式
- 然后通过你在配置文件中配置好并实现你需要的实现类,然后供其进行调用完成,类似下面
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance
(3)dubbo是如何实现的这个增强版的SPI机制的?源码级别
- 首先dubbo其实是通过 ExtensionLoader 的 getExtensionLoader 方法获取一个ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象
- getExtension方法中的核心就是创建一个扩展对象
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
// 获取默认的拓展实现类
return getDefaultExtension();
}
// Holder,顾名思义,用于持有目标对象
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
// 双重检查
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建拓展实例
instance = createExtension(name);
// 设置实例到 holder 中
holder.set(instance);
}
}
}
return (T) instance;
}
- 然后到createExtension()这个方法中主要的流程是:
- 通过 getExtensionClasses 获取所有的拓展类
- 通过反射创建拓展对象
- 向拓展对象中注入依赖(这里使用的是 Dubbo IOC 与 AOP 的具体实现)
- 将拓展对象包裹在相应的 Wrapper 对象中(这里使用的是 Dubbo IOC 与 AOP 的具体实现)
private T createExtension(String name) {
// 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 通过反射创建实例
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 向实例中注入依赖
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
// 循环创建 Wrapper 实例
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
instance = injectExtension(
(T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("...");
}
}
- 然后是getExtensionClasses()方法去加载,如果 classes 仍为 null,则通过 loadExtensionClasses 加载拓展类
private Map<String, Class<?>> getExtensionClasses() {
// 从缓存中获取已加载的拓展类
Map<String, Class<?>> classes = cachedClasses.get();
// 双重检查
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 加载拓展类
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
- 然后是loadExtensionClasses()方法,主要做了两件事:一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件
private Map<String, Class<?>> loadExtensionClasses() {
// 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
// 对 SPI 注解内容进行切分
String[] names = NAME_SEPARATOR.split(value);
// 检测 SPI 注解内容是否合法,不合法则抛出异常
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension...");
}
// 设置默认名称,参考 getDefaultExtension 方法
if (names.length == 1) {
cachedDefaultName = names[0];
}
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
// 加载指定文件夹下的配置文件
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
总结dubbo的SPI机制的特点:(符合Dubbo的微内核+插件的总体设计思想)
首先dubbo的核心都是基于Dubbo的微内核+插件的机制,而SPI机制更是dubbo中最核心之一的地方,他的设计符合开闭原则,有很好的扩展性。
- 对Dubbo进行扩展,不需要改动Dubbo的源码,自定义的Dubbo的扩展点实现,是一个普通的Java类,Dubbo没有引入任何Dubbo特有的元素,对代码侵入性几乎为零。
- 将扩展注册到Dubbo中,只需要在ClassPath中添加配置文件。使用简单。而且不会对现有代码造成影响。符合开闭原则
- Dubbo的扩展机制支持IoC,AoP等高级功能
- Dubbo的扩展机制能很好的支持第三方IoC容器,默认支持Spring Bean,可自己扩展来支持其他容器,比如Google的Guice
- 切换扩展点的实现,只需要在配置文件中修改具体的实现,不需要改代码。使用方便
(三)dubbo中的SPI机制和Java中的SPI机制有什么区别?
看完上面的讲解,想必你自己肯定也能猜到dubbo的SPI机制和Java中的SPI机制有什么区别,不然我这篇博客不是白写了吗,哭唧唧。。。。欢迎探讨