概述
扩展点加载机制在dubbo框架中无处不在, 它也是dubbo 高扩展性的关键实现. 所以要彻底理解dubbo的实现原理, 弄明白扩展点加载机制至关重要.
dubbo 的扩展点机制本质上是在Java SPI 基础 上改造的. 那么什么是Java 的SPI 呢
所有技术的衍生都是基于业务需要,因为有需要,才会想解决办法, 进而产生各种技术. 那么Java的SPI 的产生又是基于什么样的需求呢?
我们知道JDBC是为了统一数据库的操作而由Sun 公司定制的一套接口标准, 而具体的实现则由各个数据库厂商提供, 比如我们使用mysql数据库,那么我们就要引用mysql的依赖, 这个依赖就是对JDBC的实现. 那么Java 虚拟机怎么知道接口的实现是哪个呢, 就是通过Java的SPI 机制
SPI机制的核心类是ServiceLoader, 通过调用ServiceLoader.load(接口.class) 方法加载该接口的所有实现.
load 方法的实现原理是 去 METSA_INF/services 目录寻找文件名为该接口名的文件, 然后逐行加载该文件中写的接口的实现类
使用JDBC的例子来理解就是 Java 定制了一套数据库接口, 然后数据库厂商提供实现类,然后在他们项目的 META-INF/services包 创建文件名为接口全类名的文件, 文件内容写他对这个接口的实现类的全类名.
比如我们引入了mysql的依赖, Java虚拟机通过这个规则就会加载到对应的实现了.
我们可以看到使用这种加载机制, 可以很好的将接口和实现分离, 极大的提高了灵活性和扩展性.
dubbo 扩展点加载机制在此基础上做了扩展和改进
- dubbo 加载的路径包括 META-INF/services, META-INF/dubbo 和 META-INF/dubbo/internal
- dubbo 可以按扩展名来使用不用的扩展类, Java SPI 是将实现类的全类名直接写在文件中, 而 dubbo 的形式是 扩展名=扩展实现全类名,因此可以按扩展名去使用对应的扩展类
- dubbo 是按需实例化的, 使用Java的ServiceLoader.load 方法加载完实现类,它会对所有的实现类都实例化. 而dubbo 是根据你要使用的那个扩展名来实例化对应的实现的, 其他的扩展类放在缓存中
- dubbo 增加实现了 类似AOP和IOC的功能
三个关键注解
dubbo 扩展点加载机制有三个重要的注解,分别是
- @SPI: 主要是标记功能, 标记一个接口是Dubbo SPI 接口, 也就是表明它是一个扩展点, 可以有多个不同的实现
- @Adaptive: 标注在类上, 表示这个类是该扩展点的默认实现, 最多只能有一个扩展点实现使用这个注解, 也就是一个扩展点的自适应实现智能有一个; 标注在方法上, dubbo 框架会为这个扩展点生成一个默认的自适应实现.
- @Activate: 主要的使用场景是 在不同的场景中需要激活不同的扩展点实现
ExtensionLoader的实现原理
ExtensionLoader 的构造方法是私有的, 使用方法是通过调用静态方法来获取一个ExtensionLoader实例,然后调用getExtension(String name) 方法来加载以name为扩展名的实现
即常见用法是
ExtensionLoader.getExtensionLoader(扩展点.class).getExtension(扩展名)
getExtensionLoader 方法首先会检查传入的类型是否为一个合法的扩展点,也就是是否是一个有@SPI 注解的接口
然后从 EXTENSION_LOADERS 缓存中获取该type对应的扩展点加载器,如果缓存中没有再新建一个, 为什么要从缓存中获取扩展点加载器呢?
因为dubbo 对扩展点的实现设置了很多缓存, 比如扩展点的实现类,自适应类,包装类等等, 这些缓存就保存在这个扩展点对应的扩展点加载器中.
可以看到在构造方法中 objectFactory 也是通过加载扩展点实现来赋值的, objectFactory 具体在给扩展点注入其他扩展点属性的时候会使用到, ExtensionFactory 在后面再详细解析
// 静态的,全局唯一
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
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;
}
// 私有的静态方法
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory =
(type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
ExtensionLoader 有三个获取扩展点实现的方法, 分别是 getExtension, getAdaptiveExtension 和getActivateExtension 分别获取普通扩展点实现, 自适应扩展点实现, 自动激活的扩展点实现
普通扩展类: 除了自适应扩展类, 包装扩展类,其他的就是普通扩展类
自适应扩展类: 有@Adaptive注解的扩展类, 或者扩展点接口中的方法有@Adaptive注解的, 此时 dubbo 框架会动态生成一个
自动激活扩展点: 主要使用在有多个扩展实现, 需要根据不同条件被激活的场景, 比如Filter需要多个同时被激活.
getExtension
参数说明:
name: 扩展名
wrap: 是否对扩展实例进行包装, 默认为真, 封装后面会详细解析
总体流程:
1. 如果name 为"true" 则使用默认的实现,后面会解析
2. 先从cachedInstances 普通扩展实例缓存中获取, 如果没有先new一个Holder占个坑
3. 然后使用double-check 机制对holder加锁, 然后调用 createExtension 创建实例,在放到holder中
这里有个小问题,为什么要使用holder,并且加锁呢? cachedInstances 已经使用了 ConcurrentHashMap,可以保证线程安全啊?
其中一个原因, 是因为要获取一个扩展点的实例, 需要加载这个扩展点的所有实现类, 实例化之后还要对实例进行依赖注入和包装, 最后才是一个完整的实例,如果直接在实例化完成后放入ConcurrentHashMap, 由 ConcurrentHashMap 保证只有一个 实例, 那么可能会导致多个线程都执行了实例化的过程, 而最后只有一个线程的结果被采用, 造成浪费
public T getExtension(String name) {
return getExtension(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();
}
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;
}
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
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;
}
// Holder 实现
public class Holder<T> {
// volatile 保证了线程间可见
private volatile T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
createExtension
总体流程:
1. 通过 getExtensionClasses 方法获取该type类型的扩展点的所有实现类, 然后拿到扩展名为name的实现类
2. 从EXTENSION_INSTANCES 缓冲中获取该扩展类的实例,如果没有则新构造一个
3. 调用 injectExtension(instance) 方法对该实例进行属性注入
4. 如果要进行包装, 则遍历排序后的 cachedWrapperClasses (在getExtensionClasses过程中会进行缓存), 用 instance 去实例化包装类,然后对包装类的实例调用 injectExtension 方法进行属性注入之后, 作为新的 instance. 我们看到如果有多个包装类会进行层层包装
5. 调用initExtension(instance) 方法
// 静态的,全局唯一, 一个扩展点实现类只会保存一个实例
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
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))) {
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);
}
}
private void initExtension(T instance) {
if (instance instanceof Lifecycle) {
Lifecycle lifecycle = (Lifecycle) instance;
lifecycle.initialize();
}
}
getExtensionClasses
一样的套路,先尝试从缓存中获取, 如果没有再通过 loadExtensionClasses 加载
这里同样是锁着占位坑 holder
private final