文章目录
一、SPI介绍
引自Dubbo官方教程:https://dubbo.apache.org/zh/docs/v2.7/dev/source/dubbo-spi/
SPI 全称为 Service Provider Interface,是一种服务发现机制,是 Java 提供的一套用来被第三方实现或者扩展的 API。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。
二、JDK SPI
1. 实现原理
JDK 自带的 SPI 是 通过 ServiceLoader 类实现,原理是通过读取 META-INF/services
下的文件名为接口全限定名
文件内容来加载实现类,文件内容为接口实现类的全限定名。如 JDBC 就是使用的 SPI,JDK 只负责定义数据库驱动接口,交给第三方来具体实现(如 Oracle、MySQL 等)。
以 MySQL 为例,在mysql-connector-java-5.1.48.jar
包中可看到 META-INF/services
目录下有个java.sql.Driver
文件,内容为:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
在 JDK DriverManager 中使用 SPI 加载 JDBC 驱动,此时会加载 MySQL 驱动,以上文件中的两个类都会进行加载。
// java.sql.DriverManager#loadInitialDrivers # 586 行
// SPI 加载 JDBC 驱动
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
while(driversIterator.hasNext()) {
driversIterator.next();
}
在 com.mysql.jdbc.Driver
类中,有一段静态代码块会在加载的时候执行,即往 DriverManager 注册驱动:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
2. 使用示例
- 定义接口 Robot:
public interface Robot {
void sayHello();
}
- 定义两个 Robot 的实现类:
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
- META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 com.au.spi.Robot。文件内容为实现类的全限定名,如下:
com.au.spi.OptimusPrime
com.au.spi.Bumblebee
- 启动类:
public class Main {
public static void main(String[] args) throws Exception {
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("Java SPI");
serviceLoader.forEach(Robot::sayHello);
}
}
- 运行结果:
Java SPI
Hello, I am Optimus Prime.
Hello, I am Bumblebee.
三、Dubbo SPI
1. 实现原理
JDK 自带的 SPI 功能比较简单,并不方便扩展,如 AOP 等,并且不能按需获取,一个文件里面的实现类都会进行顺序加载,事实上并不是所有类都有用,就会导致资源浪费,这不符合 Dubbo 的作风,所以 Dubbo 没有采用 JDK SPI,重新实现了一套功能更强的 SPI 机制,Dubbo SPI 的相关逻辑都是封装在 ExtensionLoader 类中,通过 ExtensionLoader 可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,文件名也是需要扩展的接口的全限定名,配置内容是键值对,如下:
optimusPrime = com.au.spi.OptimusPrime
bumblebee = com.au.spi.Bumblebee
2. 使用示例
- 定义接口 Robot:
@SPI
public interface Robot {
void sayHello();
}
- 定义两个 Robot 的实现类:
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
- META-INF/dubbo 文件夹下创建一个文件,名称为 Robot 的全限定名 com.au.spi.Robot。文件内容为键值对,name (随意) = 实现类的全限定名,如下:
optimusPrime = com.au.spi.OptimusPrime
bumblebee = com.au.spi.Bumblebee
- 启动类:
public class Main {
public static void main(String[] args) throws Exception {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
System.out.println("Dubbo SPI");
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}
- 运行结果:
Dubbo SPI
Hello, I am Optimus Prime.
Hello, I am Bumblebee.
3. 源码分析
注:源码基于 2.7.8-release 分支。源码地址:https://github.com/apache/dubbo
3.1. 获取 ExtensionLoader 实例
从 ExtensionLoader.getExtensionLoader
入口开始分析,该方法是获取一个 ExtensionLoader 实例,首先会判断扩展类的类型是否为接口,再判断接口上是否有 @SPI 注解,不满足就会报错。然后从缓存中获取该扩展类对应的 ExtensionLoader 实例,如果缓存未命中,则创建一个新的实例,并加入缓存中。
在 Dubbo SPI 中一个扩展点只有一个ExtensionLoader 实例。
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!");
}
// 扩展类接口上必须带有 @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<>(64);
// 从缓存中获取 ExtensionLoader 实例
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;
}
3.2. 获取拓展类对象
name 如果等于 “true” ,则获取默认的扩展类实例,否则去缓存中获取扩展类实例,如果缓存未命中,则创建一个扩展类实例。
public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
// 获取默认的拓展实例
return getDefaultExtension();
}
// Holder,顾名思义,用于持有目标对象,这里目标对象就是扩展类实例
// 从缓存中获取Holder对象,如果未命中缓存,则创建一个Holder对象
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
// double check,单例模式
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建拓展实例
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}
// private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
// 从缓存中获取Holder对象,如果未命中缓存,则创建一个Holder对象
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;
}
3.3. 创建扩展类对象实例
- 通过 getExtensionClasses 获取所有的扩展类
- 通过反射创建扩展对象
- 向拓展对象中注入依赖,IOC 实现
- 将拓展对象包裹在相应的 Wrapper 对象中,即 AOP 的实现
- 执行生命周期 initialize 方法
private T createExtension(String name, boolean wrap) {
// 获取所有的拓展类,也是先从缓存获取,未命中再去读取配置文件,见下面分析
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
// private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
// 先从实例缓存中获取,未命中则通过反射创建实例对象,并放入缓存
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// Dubbo IOC 实现,注入依赖
injectExtension(instance);
// 是否需要包装
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
// cachedWrapperClasses 在扩展类加载阶段会进行赋值(loadClass方法中)
if (cachedWrapperClasses != null) {
// 如果有包装类,将这些包装类排序、反转
// @Activate 注解中有 order 字段,字段值越小优先级越低
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
// 循环创建 Wrapper 实例,
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
// Dubbo AOP 实现
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
// 触发生命周期 initialize 方法
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
3.4. 获取所有的拓展类
从缓存中获取所有扩展类,如果缓存未命中,则执行 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;
}
3.5. 加载所有扩展类
- 缓存默认扩展名
- 遍历加载策略(加载策略是通过 JDK SPI 来加载的,详见 loadLoadingStrategies() 方法,在类加载的时候执行)
- 根据加载策略去加载扩展类
private Map<String, Class<?>> loadExtensionClasses() {
// 缓存默认扩展名,即从 @SPI 注解中获取 value 的值,在 getDefaultExtension() 方法中会用到这个默认扩展名,这个值不能为 ”true“,否则会报错
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 通过策略来加载配置文件中的扩展类
// 这个策略是通过 JDK SPI 来加载的,配置在 resources/META-INF/services/org.apache.dubbo.common.extension.LoadingStrategy
// 具体详情可以看 loadLoadingStrategies() 方法,比较简单
// private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
// 兼容老版本,阿里 dubbo
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
3.6. 加载策略中配置的所有目录文件
通过策略中配置的目录 + 扩展类全限定名作为文件名,通过类加载器获取所有 jar 包中的同名文件,然后遍历这些文件,一个一个读取并解析配置,最终加载解析出来的扩展类。
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);
}
}
// 遍历这些文件,不同 jar 包中可以有多个同名文件
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 读取文件并解析
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
3.7. 读取并解析配置文件
一行一行读取,先把注释去掉(# 后面的为注释),再找到等号进行分割,去掉空格,这样就得到了 name 和 line 两个字符串,分别是扩展名和扩展类全限定类名,得到这两个后再去加载扩展类。
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);
}
}
3.8. 加载单个扩展类
- 先判断配置文件中的类是否和 type 同类型,不是则报错(都不同类型还扩展啥)。
- 判断是否存在 @Adaptive 注解,如果存在,则缓存这个类为自适应类(注意,一种扩展类型只能有一个 @Adaptive 标注的类,否则会报错)。
- 判断是否包装类,如果是,则缓存这个类为包装类,用来实现 AOP 功能。
- 以上都不是,走到 else 分支,说明这个类是普通的扩展类。先执行扩展类的默认构造器,再判断配置文件中的 key 是否为空,如果为空,则尝试从 @Extension 注解中获取 name,或使用小写的类名作为 name。最后通过逗号分割 name,存储 name 到 Activate 注解对象的映射关系、Class 到名称的映射关系、名称到 Class 的映射关系(名称到 Class 的映射关系即回填 loadExtensionClasses() 的返回值-所有的扩展类)。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name, boolean overridden) throws NoSuchMethodException {
// 配置文件中配置的扩展类不是 type 类型的直接报错,都不同类型还扩展啥
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 注解,如果存在,则缓存这个类为自适应类
// 注意,一种扩展类型只能有一个 @Adaptive 标注的类,否则会报错,可以点进去 cacheAdaptiveClass 方法看看
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
// 是否包装类,如果是,则缓存这个类为包装类,用来实现 AOP 功能
cacheWrapperClass(clazz);
} else {
// 执行扩展类的默认构造器,所以扩展类不能将默认构造器设为私有,否则会报错
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// // 如果 name 为空,则尝试从 @Extension 注解中获取 name,或使用小写的类名作为 name
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
// 通过逗号分割 name
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,
// 存储 name 到 Activate 注解对象的映射关系
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 存储 Class 到名称的映射关系
cacheName(clazz, n);
// 存储名称到 Class 的映射关系,即回填 loadExtensionClasses() 的返回值-所有的扩展类
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
// 查找注解 @Extension 的 value 值,如果没有,则取类名的小写
private String findAnnotationName(Class<?> clazz) {
org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
if (extension != null) {
return extension.value();
}
String name = clazz.getSimpleName();
if (name.endsWith(type.getSimpleName())) {
name = name.substring(0, name.length() - type.getSimpleName().length());
}
return name.toLowerCase();
}
3.9. Dubbo IOC 实现
Dubbo IOC 是通过类的 set 方法来注入依赖的,没有解决循环依赖的问题。
- 如果对象工厂 objectFactory 为空,直接返回 instance。
- 遍历实例的所有方法,找到 set 开头并且只有一个入参的 public 方法,获取属性名(比如 setName 方法对应属性名 name)作为 bean 名字到对象工厂中获取到依赖对象。
- 通过反射执行 set 方法进行依赖注入。
private T injectExtension(T instance) {
// 对象工厂为空,不执行直接返回
if (objectFactory == null) {
return instance;
}
try {
// 遍历实例的所有方法
for (Method method : instance.getClass().getMethods()) {
// 找到 set 开头并且只有一个入参的 public 方法
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
// 如果有方法上面有 DisableInject 注解,则不进行注入
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
// 如果是原始类型,不执行,如 Array、Integer、String、Boolean、Charater、Number、Date
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 获取属性名,比如 setName 方法对应属性名 name
String property = getSetterProperty(method);
// 从 ObjectFactory 中获取依赖对象
// ObjectFactory 是在创建 ExtensionLoader 对象时候进行初始化的,涉及到 SPI 扩展点自适应机制的内容
// objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通过反射执行 set 方法进行依赖注入
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
3.10. 获取默认的扩展类对象实例
- 获取所有的拓展类,这个过程中会存储默认的扩展类名,上面分析过。
- 执行 getExtension 方法获取默认扩展类。
public T getDefaultExtension() {
getExtensionClasses();
if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
return null;
}
return getExtension(cachedDefaultName);
}