Dubbo的SPI机制的实现的源码解析
java的SPI
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。典型的应用实例有jdbc的第三方实现,日志框架的实现等。
优点:
可以实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
缺点:
-
必须在特定的位置增加配置文件
-
ServiceLoader必须加载接口的所有的实现,获取某种实现只能遍历获取
-
ServiceLoader并非线程安全的
Spring的SPI机制
Spring有实现自己的spi机制,与java的spi有点不同。 Springboot的各个starter的引入即生效的能力就是使用的其SPI实现的。
在java包中的META-INF目录中新增spring.factories文件。 增加key=value形式的键值对, 其中key是接口的全路径类名, value是这个接口的实现类的全路径名, spring就会把文件中的实现类注入到容器中了。
通过这种方式, 第三方的jar包只要导入到classpath路径中就会自动注入到spring容器中
Dubbo的SPI机制
DUBBO也实现了自己的SPI机制,并且dubbo大量使用spi。 通过dubbo的spi可以很方便的替换dubbo中的各个组件的实现等,十分的灵活,本文主要着重的说明dubbo的spi是如何实现的。
dubbo的spi 还支持注入其他的SPI实现类的功能,提供了类似的IOC功能
使用dubbo的约束:
- 在jar包中如下META-INF/dubbo中增加子目录或文件,
- 文件名必须是接口的全路径类名
- 文件内容是key-value对的形式, 其中key是这个实现的名称, value是实现的类的全路径类名。
我们先看下一个DUBBO源码中的使用示例:
org.apache.dubbo.container.Main
类源码如下:
public class Main {
public static final String CONTAINER_KEY = "dubbo.container";
public static final String SHUTDOWN_HOOK_KEY = "dubbo.shutdown.hook";
private static final Logger logger = LoggerFactory.getLogger(Main.class);
//获取Container接口的所有符合的SPI中的实现类
private static final ExtensionLoader<Container> LOADER = ExtensionLoader.getExtensionLoader(Container.class);
private static final ReentrantLock LOCK = new ReentrantLock();
private static final Condition STOP = LOCK.newCondition();
// main方法中就是获取所有的符合要求的实现类,并遍历调用下,
public static void main(String[] args) {
try {
if (ArrayUtils.isEmpty(args)) {
String config = ConfigUtils.getProperty(CONTAINER_KEY, LOADER.getDefaultExtensionName());
args = COMMA_SPLIT_PATTERN.split(config);
}
final List<Container> containers = new ArrayList<Container>();
for (int i = 0; i < args.length; i++) {
containers.add(LOADER.getExtension(args[i]));
}
logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
if ("true".equals(System.getProperty(SHUTDOWN_HOOK_KEY))) {
Runtime.getRuntime().addShutdownHook(new Thread("dubbo-container-shutdown-hook") {
@Override
public void run() {
for (Container container : containers) {
try {
container.stop();
logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
} catch (Throwable t) {
logger.error(t.getMessage(), t);
}
try {
LOCK.lock();
STOP.signal();
} finally {
LOCK.unlock();
}
}
}
});
}
for (Container container : containers) {
container.start();
logger.info("Dubbo " + container.getClass().getSimpleName() + " started!");
}
System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Dubbo service server started!");
} catch (RuntimeException e) {
logger.error(e.getMessage(), e);
System.exit(1);
}
try {
LOCK.lock();
STOP.await();
} catch (InterruptedException e) {
logger.warn("Dubbo service server stopped, interrupted by other thread!", e);
} finally {
LOCK.unlock();
}
}
}
@SPI("spring")
public interface Container {
/**
* start method to load the container.
*/
void start();
/**
* stop method to unload the container.
*/
void stop();
}
ExtensionLoader
这个类是实现dubbo扩展机制的核心类。 下面讲述的源码都是这个类中的。
ExtensionLoader.getExtensionLoader(Container.class);
private static final ExtensionLoader<Container> LOADER = ExtensionLoader.getExtensionLoader(Container.class);
示例中最重要的就是这一句。 这一句就是获取到了实现类包装加载类ExtensionLoader(ExtensionLoader我称之为包装加载类)
getExtensionLoader 方法源码
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//简单的包装传入的类型,并放入缓存中。
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
这个方法的作用主要是简单的生成ExtensionLoader类,并放入缓存中。
####ExtensionLoader类的构造方法
private ExtensionLoader(Class<?> type) {
this.type = type;
//这一句中会加载ExtensionFactory的spi扩展
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
在构造方法中会加载ExtensionFactory类的spi扩展, getExtensionLoader方法是一样的,我们看下getAdaptiveExtension方法。
getAdaptiveExtension
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//如果缓存中没有这个类就会创建个
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
方法中都是先在缓存中寻找,如果没有就会调用createAdaptiveExtension方法,我们继续深入
private T createAdaptiveExtension() {
try {
//这里injectExtension会注入依赖,我们现在先看下是如何找到ExtensionFactory的spi扩展的
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
再次深入getAdaptiveExtensionClass方法
getAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
//这里应该会获取扩展的类
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getExtensionClasses
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
/**
* synchronized in getExtensionClasses
*/
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
方法中遍历strategies集合, 使用LoadingStrategy来加载SPI扩展.
LoadingStrategy是DUBBO中的SPI扩展的扫描策略接口,dubbo中默认了四种实现, dubbo主要是使用这四种实现来加载spi扩展的
- DubboExternalLoadingStrategy : dubbo用来加载META-INF/dubbo/external/目录下的spi扩展
- DubboInternalLoadingStrategy : dubbo用来加载META-INF/dubbo/internal/目录下的spi扩展
- DubboLoadingStrategy: dubbo用来加载 META-INF/dubbo/目录下的spi扩展
- ServicesLoadingStrategy: dubbo用来加载 META-INF/services/目录下的spi扩展
我们看下具体的实现loadDirectory方法, 为何有两个方法,是因为dubbo是由阿里巴巴转给Apache社区了,转后很多的类的包名都修改成org.apache类,但是为了和以前的兼容,所以以前的包名也加载一次。
loadDirectory
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
if (urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
//前面的代码都是资源定位,获取资源路径的url。 这个方法才是真正的加载逻辑
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
一堆的资源加载逻辑,获取url, 真正的解析文件获取class还是在loadResource 方法中。
loadResource
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
//核心方法在这里
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
这个方法中会读取文件中每一行,并且每行加载一个类。 加载方法loadClass. 在调用loadClass方法的时候,在传入参数的时候其实已经已经通过Class.forName方法获取到具体的扩展实现类了。
loadClass
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 放入缓存
cacheActivateClass(clazz, names[0]);
for (String n : names) {
//缓存
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
这个方法主要解析扩展类的name, 并且将扩展类放入缓存中。
loadClass的第一个参数extensionClasses 是从loadExtensionClasses方法就开始传入的。 主要是用来装载加载的spi类, 并且是map类型,key存的是name,在loadClass的 saveInExtensionClass 方法中就是把加载的类放入extensionClasses中。
我们回到开始getAdaptiveExtensionClass方法中
这个方法是dubbo的Adaptive机制的主要实现逻辑, 关于Adaptive机制的详情可以看下下面这篇文章:https://blog.csdn.net/weixin_33967071/article/details/92608993。
Adaptive说明:如果dubbo的spi接口有多个扩展实现,那么具体使用那个扩展实现呢, 这个通过Adaptive机制就可以解决这个问题,它便没有写死使用那个扩展,而是通过你传入的URL(dubbo的)参数来动态确定你要调用的是那个扩展实现。
缺点:
-
实现方法必须带有URL参数。
-
必须在接口中增加 Adaptive注解,没有增加这个注解的方法调用都会抛出异常。
总结:
到这里就可以知道,dubbo是通过扫描所有jar包中的特定目录下的文件,根据文件名称和文件中的内容来加载扩展类的。 如果有多个扩展实现类就会都加载到缓存中,提供了多种方法可以获取到具体的实现,但是Adaptive机制才是使用来确定实现类的最多的方式。