SPI机制DEMO示例
先写个简单SPI例子:
@SPI
public interface Color {
void getColor();
}
public class Red implements Color {
@Override
public void getColor() {
System.out.println("Hello. I am red!");
}
}
public class Yellow implements Color {
@Override
public void getColor() {
System.out.println("Hello. I am yellow!");
}
}
public class ColorWrapper1 implements Color {
private Color color;
public ColorWrapper1(Color color){
this.color = color;
}
@Override
public void getColor() {
System.out.println("this is colorWrapper1");
color.getColor();
}
}
public class ColorWrapper2 implements Color {
private Color color;
public ColorWrapper2(Color color){
this.color = color;
}
@Override
public void getColor() {
System.out.println("this is colorWrapper2");
color.getColor();
}
}
在META-INF/dubbo路径下创建文件com.ghh.dubbo.spi.spidemo.service.Color,文件内容如下:
red = com.ghh.dubbo.spi.spidemo.service.impl.Red
yellow = com.ghh.dubbo.spi.spidemo.service.impl.Yellow
colorWrapper1 = com.ghh.dubbo.spi.spidemo.service.impl.ColorWrapper1
colorWrapper2 = com.ghh.dubbo.spi.spidemo.service.impl.ColorWrapper2
测试:
public class DubboSpiTest {
@Test
public void getColor() {
ExtensionLoader<Color> extensionLoader = ExtensionLoader.getExtensionLoader(Color.class);
Color red = extensionLoader.getExtension("red");
red.getColor();
Color yellow = extensionLoader.getExtension("yellow");
yellow.getColor();
}
}
结果:
this is colorWrapper1
Hello. I am red!
this is colorWrapper2
this is colorWrapper1
Hello. I am yellow!
可以看到,两个实现类都被加载了。
源码讲解
在讲解之前,我们先定一下文中的几个概念:
- 扩展接口:指被@SPI注解过的接口。
- 扩展实现类:指实现了扩展接口的实现类。
- 扩展实现包装类:指持有扩展接口属性的实现类。
- 扩展对象:由扩展实现类生成的实例对象。
- 配置名称:配置文件中配置的key值。
下面开始Debug,在ExtensionLoader.getExtensionLoader(Color.class);
行打上断点,进入getExtensionLoader方法:
// 此处type,传入的就是被SPI注解的扩展接口
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!");
}
// 此方法是判断type这个接口上是不是有SPI注解,如果没有,就报错
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
// private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
// EXTENSION_LOADERS是一个被static修饰的ConcurrentHashMap,此处根据type从缓存中获取了一个ExtensionLoader对象
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
// 当缓存中没有loader时,新创建一个ExtensionLoader对象
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
EXTENSION_LOADERS
是一个static属性,这表明它是线程共享的。
再顾名思义一下,ExtensionLoader为扩展实现类加载器,鉴于上面测试方法,在此大胆猜测loader中存储的是从com.ghh.dubbo.spi.spidemo.service.Color配置文件中加载的内容。
关于ExtensionLoader对象如何构造的先不管,不能打断我们debug的节奏,先把这个问题遗留下来。先看后面一步getExtension(name)
方法:
getExtension(String name)
// 此处name为扩展实现类的配置名称
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
// 如果传入的配置名称是true,表示要获取的是默认扩展对象
// 【问题1】此处默认扩展实现类是何时设定的?
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 final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
// 通过此方法可以看出,ExtensionLoader对象中的cachedInstances就是保存配置文件中【配置名称:扩展对象】映射关系的
private Holder<Object> getOrCreateHolder(String name) {
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
return holder;
}
得到关键信息:cachedInstances
这个Map保存的东西为【配置名称:扩展对象】。
createExtension(String name)
接着看创建扩展实现类对象的方法:
private T createExtension(String name) {
// getExtensionClasses()为加载配置文件中内容的方法,加载完后,根据传入的配置名称获取扩展实现类
//【问题2】配置文件如何加载的?
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
// private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
// EXTENSION_INSTANCES 是保存的是【扩展实现类:扩展对象】
// 如果缓存中没有扩展对象,则通过反射创建
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 对扩展对象进行依赖注入
// 【问题3】依赖注入做了什么?
injectExtension(instance);
// private Set<Class<?>> cachedWrapperClasses;
// cachedWrapperClasses是保存扩展实现包装类的集合
// 【问题4】cachedWrapperClasses是何时初始化的?
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
// 循环创建扩展包装对象,并将当前扩展对象传入,循环创建并重新赋值的方式使得多个包装类形成了层层嵌套的关系
// 这里也是Dubbo SPI机制中AOP的体现,其最终效果类似于过滤器
for (Class<?> wrapperClass : wrapperClasses) {
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);
}
}
循环创建扩展包装对象,并将当前扩展对象传入,循环创建并重新赋值的方式使得多个包装类形成了层层嵌套的关系,这里也是Dubbo SPI机制中AOP的体现
,最后返回的对象如下所示,而且注意,配置在最后面的扩展实现包装类在最外层,其中的逻辑也会先执行。
到这里我们已经获取到我们需要的东西了。先小结一下:
- 通过 getExtensionClasses 获取配置文件中所有的扩展实现类。
- 通过反射创建扩展对象。
- 向扩展对象中进行依赖注入。
- 根据配置文件中配置的Wrapper 对象进行层层包装,最终返回包装后的扩展包装对象。
下面来解答【问题2】,看看getExtensionClasses
是如何加载配置文件的:
private Map<String, Class<?>> getExtensionClasses() {
// private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
// 从缓存中获取,cachedClasses保存的是配置文件中【配置名称:扩展实现类】对应关系
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
// 缓存中没有当前接口的缓存实现类,则进行加载
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
所以,加载的关键方法就是下面这个:
private Map<String, Class<?>> loadExtensionClasses() {
// 【解答3】设置缓存中的默认实现类对应的配置名称
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// internal extension load from ExtensionLoader's ClassLoader first
// private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
// private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
// private static final String SERVICES_DIRECTORY = "META-INF/services/";
// 通过loadDirectory方法分别加载三个路径下文件夹中的文件
// 先加载META-INF/dubbo/internal/,再加载META-INF/dubbo/,最后加载META-INF/services/
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);
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;
}
private void cacheDefaultExtensionName() {
// 获取SPI注解中的属性(只有一个value)
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation == null) {
return;
}
// 获取value属性值
String value = defaultAnnotation.value();
// value默认为空字符串“”,如果有设置value才会执行下面的逻辑
if ((value = value.trim()).length() > 0) {
// private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
// 根据,分隔
String[] names = NAME_SEPARATOR.split(value);
// 如果设置的值多于一个,则报错
if (names.length > 1) {
throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
// private String cachedDefaultName;
// 将cachedDefaultName的值设为SPI注解中的value值
if (names.length == 1) {
cachedDefaultName = names[0];
}
}
}
这里可以回答【问题1】了,如果@SPI注解中设置了值,那么这个值对应的扩展实现类就是默认扩展实现类,当执行extensionLoader.getExtension("true");
时,就会返回注解中设置名称对应的扩展对象。
例如:Color类上设置@SPI(“yellow”),那么extensionLoader.getExtension(“true”)返回的就是yellow对应的扩展对象。
下面再来看看loadDirectory
方法:
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst) {
// 文件名称:文件夹路径+扩展接口全限定名
// 这就是为什么我们创建的文件名是com.ghh.dubbo.spi.spidemo.service.Color
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader();
// 获取配置文件URL
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();
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
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;
// 如果设置了等号,根据等号分离出配置荐的key和value,分别对应name及实现类全限定名
// 如果没有等号,则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) {
// 读取一行内容就加载一个类
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} 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);
}
}
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.");
}
// private volatile Class<?> cachedAdaptiveClass = null;
// 判断扩展实现类上是不是有@Adaptive注解,如果有,则缓存在cachedAdaptiveClass变量中,且如果有多于一个扩展实现类有此注解,会报错
// 【问题5】Adaptive是为什么而存在的?
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
// private Set<Class<?>> cachedWrapperClasses;
// 【解答4】通过判断扩展实现类中是不是有以扩展接口为入参的构造器来判断是不是一个包装类
// 如果是,则缓存在cachedWrapperClasses集合中
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
// 创建扩展对象,目的确定有默认构造类
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 如果配置文件中没有设置扩展名称,则通过下面方法设置
// 1、如果扩展实现类上有@Extension("value")注解,那以这个value作为扩展名称
// 2、如果没有@Extension注解,那么以扩展实现类的simplename为扩展名称
// 而且会去掉以接口结尾的部分,例:如果Red定义为RedColor,则它的扩展名称还是red
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)) {
// private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
// 如果类上有@Activate注解,则在cachedActivates保存【第1个配置名称:Activate注解对象】
// 【问题6】此处有何作用?
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
// 在cachedNames中存储【扩展实现类:配置名称】
cacheName(clazz, n);
// 将【配置名称:扩展实现类】保存在extensionClasses中,最终会保存在类属性cachedClasses中
// 此处可以看出,同一个扩展实现类可能有多个配置名称,配置时以逗号分隔
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
总结
通过调用ExtensionLoader.getExtensionLoader(Class).getExtension(name);会触发Class这个SPI扩展接口所有扩展实现类的加载以及name这个配置名称对应的扩展对象的创建。
加载完成后,类属性分别存储了如下内容:
// 非包装类和Adaptive类【扩展实现类:扩展对象】
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
// 扩展接口全限定名
private final Class<?> type;
//
private final ExtensionFactory objectFactory;
// 非包装类和Adaptive类的【扩展实现类:配置名称】
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
// 非包装类和Adaptive类的【配置名称:扩展实现类】
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
// 【第1个配置名称:Activate注解对象】
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
// 【配置名称:扩展对象】
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
//
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
// 被@Adaptive注解的扩展实现类,只有一个
private volatile Class<?> cachedAdaptiveClass = null;
// SPI注解中的value值
private String cachedDefaultName;
// 扩展实现包装类
private Set<Class<?>> cachedWrapperClasses;
遗留三个问题
- 【问题3】依赖注入做了什么?
- 【问题5】Adaptive是为什么而存在的?
- 【问题6】在cachedActivates保存【第1个配置名称:Activate注解对象】有何作用。