dubbo 扩展点加载机制

本文深入探讨了Dubbo的扩展点加载机制,该机制是其高扩展性的重要组成部分。基于Java SPI,Dubbo进行了扩展,加载路径包括META-INF/services、META-INF/dubbo和META-INF/dubbo/internal。关键注解@SPI、@Adaptive和@Activate用于标记和管理扩展点。ExtensionLoader是核心组件,负责加载、创建和管理扩展点实现。Dubbo还实现了类的AOP和IOC功能,以及自适应扩展和自动激活扩展点。文章详细介绍了ExtensionLoader的工作原理,包括getExtension、getAdaptiveExtension和getActivateExtension等方法,以及动态编译自适应扩展类的过程。
摘要由CSDN通过智能技术生成

概述

扩展点加载机制在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 扩展点加载机制在此基础上做了扩展和改进

  1. dubbo 加载的路径包括 META-INF/services, META-INF/dubbo 和 META-INF/dubbo/internal
  2. dubbo 可以按扩展名来使用不用的扩展类, Java SPI 是将实现类的全类名直接写在文件中, 而 dubbo 的形式是 扩展名=扩展实现全类名,因此可以按扩展名去使用对应的扩展类
  3. dubbo 是按需实例化的, 使用Java的ServiceLoader.load 方法加载完实现类,它会对所有的实现类都实例化. 而dubbo 是根据你要使用的那个扩展名来实例化对应的实现的, 其他的扩展类放在缓存中
  4. dubbo 增加实现了 类似AOP和IOC的功能

三个关键注解

dubbo 扩展点加载机制有三个重要的注解,分别是

  1. @SPI: 主要是标记功能, 标记一个接口是Dubbo SPI 接口, 也就是表明它是一个扩展点, 可以有多个不同的实现
  2. @Adaptive: 标注在类上, 表示这个类是该扩展点的默认实现, 最多只能有一个扩展点实现使用这个注解, 也就是一个扩展点的自适应实现智能有一个; 标注在方法上, dubbo 框架会为这个扩展点生成一个默认的自适应实现.
  3. @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 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值