概述
目录
一、基本介绍
二、自定义一波
三、看看源码
1. ExtensionLoader#getExtensionLoader(Fly.class)
2. 再到ExtensionLoader#getExtension
3. ExtensionLoader#getExtensionClasses
4. ExtensionLoader#loadExtensionClasses
5. ExtensionLoader#loadDirectory
6. loadResource->loadClass
首先看下官方怎么玩的
一、基本介绍
不同于Java SPI,Dubbo SPI是按需加载。通过名字去文件里面找到对应的实现类全限定名然后加载实例化。配置文件里面存放的是键值对,例如:
对于配置目录,Dubbo 对配置文件目录的约定(不同于 Java SPI ,Dubbo 分为了三类目录)
1. META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI
2. META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件
3. META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件
二、自定义一波
那么自定义一波,首先写好接口和实现类:
注意接口写个@SPI注解,表明要用SPI机制,值可以加,表示默认值(像用默认netty server那样)
/**
* 测试Dubbo SPI
* @author ZRH
* @version 1.0.0
* @date 2020/10/16
*/
@SPI
public interface Fly {
void fly();
}
public class Bird implements Fly{
@Override
public void fly() {
System.out.println("Bird can fly");
}
}
public class Plane implements Fly{
@Override
public void fly() {
System.out.println("Plane can fly");
}
}
然后写好测试类
import com.example.dubbo.service.Fly;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.junit.jupiter.api.Test;
public class DubboSPITests {
@Test
public void test() {
ExtensionLoader<Fly> extensionLoader = ExtensionLoader.getExtensionLoader(Fly.class);
Fly bird = extensionLoader.getExtension("bird");
bird.fly();
Fly plane = extensionLoader.getExtension("plane");
plane.fly();
}
}
写好配置(特别注意配置文件名为接口全类名):
跑起来:
三、看看源码
1. ExtensionLoader#getExtensionLoader(Fly.class)
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// type得是非空
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
// type得是接口
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
// type接口得有@SPI注解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type
+ ") is not an extension, because it is NOT annotated with @"
+ SPI.class.getSimpleName() + "!");
}
// 从缓存里面找是否已经存在这个类型的 ExtensionLoader
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
// 如果没有就新建一个塞入缓存。最后返回接口类对应的 ExtensionLoader
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
三个判断,可以看到,得有@SPI注解才能用:
private static <T> boolean withExtensionAnnotation(Class<T> type) {
return type.isAnnotationPresent(SPI.class);
}
2. 再到ExtensionLoader#getExtension
public T getExtension(String name) {
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);
holder.set(instance);
}
}
}
return (T) instance;
}
private T createExtension(String name) {
// 获取实现类,com.example.dubbo.service.Bird
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);
}
// setter依赖注入
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
// 如果有包装类则包装
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
3. ExtensionLoader#getExtensionClasses
怎么获取到它的实现类的呢?看ExtensionLoader#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;
}
4. ExtensionLoader#loadExtensionClasses
获取缓存类,有则直接返回缓存类,去那里找,没有则跳到ExtensionLoader#loadExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
// 如果SPI注释有默认值,则缓存起来
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache"
, "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache"
, "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache"
, "com.alibaba"));
return extensionClasses;
}
然后加载文件,路径就是
5. ExtensionLoader#loadDirectory
记住这里type.getName是Class获取全类名,然后看ExtensionLoader#loadDirectory
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
// 文件名出来了,META-INF/dubbo/com.example.dubbo.service.Fly
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
// 找到文件先获取所有的资源,然后一个一个去加载类
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
6. loadResource->loadClass
文件名出来了,这也就是为什么,我们自定义扩展,文件名得是接口的全类名。接着跳到loadResource->loadClass
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz
, String name) 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.");
}
// 如果有@Adaptive注解,则缓存下类
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} 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)) {
// 有@Activate注解也缓存下
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 记录全类名到类名的映射
// 比如class com.example.dubbo.service.Bird -> Bird
cacheName(clazz, n);
// put clazz in extensionClasses
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
至于@Adaptive、包装类、@Activate就留到下一章节
最后
以上就是火星上月亮为你收集整理的【Dubbo】Dubbo SPI自定义配置(源码)目录一、基本介绍二、自定义一波三、看看源码 的全部内容,希望文章能够帮你解决【Dubbo】Dubbo SPI自定义配置(源码)目录一、基本介绍二、自定义一波三、看看源码 所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复