Dubbo Spi
Dubbo SPI
Dubbo中最核心的扩展逻辑就是Spi机制了,我们今天来看
Jdk的Spi的实现
将一下为啥要用这个功能吧,主要就是扩展性强,将不同的实现进行封装,调用方去决定用哪个
我们看一下Jdk的实现,
首先需要个接口,
public interface Log {
void println();
}
添加两个的实现类
public class Log4j implements Log {
@Override
public void println() {
System.out.println("log4j println ");
}
}
public class Logback implements Log {
@Override
public void println() {
System.out.println("Logback println ");
}
}
结构如图所示,注意一下这个META-INF/services这个文件夹得创建,建议可以用个文件占位
这个META-INF/services下面就放对应的实现的接口的文件描述
com.peng.demo.spi.logImpl.Log4j
com.peng.demo.spi.logImpl.Logback
然后我看一下这个怎么获取到对应的实现
ServiceLoader<Log> serviceLoader =
ServiceLoader.load(Log.class);
Iterator<Log> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
Log log = iterator.next();
System.out.println("JDK SPI");
log.println();
}
我们看一下源码的实现
java.util.ServiceLoader#reload核心代码
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
然后这个就返回了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;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//加载 nextName字段指定的类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//初始化实例
S p = service.cast(c.newInstance());
//放到对应的缓存中
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
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();
}
};
}
}
上面是Jdk,原生的实现类,想一下它这个的弊端。
一个就是初始化加载的时候,需要将对应的实例都初始化,没办法按需加载
同时文件中只是存放对应的实例的名称,没办法做的区分,这个还是和上面逻辑类似。
Dubbo Spi的实现
首先对文件层面做了约定
分为META-INF/dubbo/services,META-INF/dubbo/internal,META-INF/services
META-INF/dubbo/services 存放用户自定义的Spi
META-INF/dubbo/internal 存放dubbo内部的Spi
META-INF/services 存放兼容jdk的Spi
同时存放的文件,也做了对应的修改,改成了kv的结构
随便找了一个看一下
curator=org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperTransporter
curator5=org.apache.dubbo.remoting.zookeeper.curator5.Curator5ZookeeperTransporter
这样我们就可以根据名字对对不同的实现进行实例化了
下面找个dubbo的spi看一下
这个是负载均衡的Spi接口
// 这个就是该接口是扩展接口,这个名称就是默认的实现
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
/**
* select one invoker in list.
*
* @param invokers invokers.
* @param url refer url
* @param invocation invocation.
* @return selected invoker.
*/
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
org.apache.dubbo.common.extension.ExtensionLoader
看一下这个具体的Spi的加载类
public class ExtensionLoader<T> {
/**
* :Dubbo 中一个扩展接口对应一个 ExtensionLoader 实例,该集合缓存了全部 ExtensionLoader 实例,其中的 Key 为扩展接口,Value 为加载其扩展实现的 ExtensionLoader 实例。
*/
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
/**
* 该集合缓存了扩展实现类与其实例对象的映射关系。
*/
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
/**
* 当前 ExtensionLoader 实例负责加载扩展接口
*/
private final Class<?> type;
private final ExtensionFactory objectFactory;
/**
* 扩展实现类和名称的关系
*/
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
/**
* 名称和扩展实现类的关系
*/
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
/**
*缓存了该 ExtensionLoader 加载的扩展名与扩展实现对象之间的映射关系。
*/
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
private volatile Class<?> cachedAdaptiveClass = null;
private String cachedDefaultName;
private volatile Throwable createAdaptiveInstanceError;
private Set<Class<?>> cachedWrapperClasses;
private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
/**
* Record all unacceptable exceptions when using SPI
*/
private Set<String> unacceptableExceptions = new ConcurrentHashSet<>();
/**
* 这个就是上面说的对应加载的策略,下面是对应的三个实现类
* DubboInternalLoadingStrategy
* DubboLoadingStrategy
* ServicesLoadingStrategy
*/
private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
}
org.apache.dubbo.common.extension.ExtensionLoader#getExtension(java.lang.String, boolean)
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {//双重检测
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//初始化扩展类
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
org.apache.dubbo.common.extension.ExtensionLoader#createExtension
private T createExtension(String name, boolean wrap) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
//缓存中获取值
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
//反射初始化
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//属性注入逻辑,下面细分析
injectExtension(instance);
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
//这个就是aop的逻辑
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
现在我们了解了Spi的作用,动态加载对应的扩展类
我们看一下这个@Adaptive作用
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.common.extension;
import org.apache.dubbo.common.URL;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Provide helpful information for {@link ExtensionLoader} to inject dependency extension instance.
*
* @see ExtensionLoader
* @see URL
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
/**
* Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
* in the URL, and the parameter names are given by this method.
* <p>
* If the specified parameters are not found from {@link URL}, then the default extension will be used for
* dependency injection (specified in its interface's {@link SPI}).
* <p>
* For example, given <code>String[] {"key1", "key2"}</code>:
* <ol>
* <li>find parameter 'key1' in URL, use its value as the extension's name</li>
* <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
* <li>use default extension if 'key2' doesn't exist either</li>
* <li>otherwise, throw {@link IllegalStateException}</li>
* </ol>
* If the parameter names are empty, then a default parameter name is generated from interface's
* class name with the rule: divide classname from capital char into several parts, and separate the parts with
* dot '.', for example, for {@code org.apache.dubbo.xxx.YyyInvokerWrapper}, the generated name is
* <code>String[] {"yyy.invoker.wrapper"}</code>.
*
* @return parameter names in URL
*/
String[] value() default {};
}
@Adaptive注解用来实现 Dubbo 的适配器功能,这个适配器的逻辑下片再写,还有上面的注入和wrapper。