趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了

奋斗吧
奋斗吧
擅长邻域:未填写

标签: 趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了 代码人生博客 51CTO博客

2023-07-22 18:24:14 280浏览

趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了,黑马,冲!


趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了_dubbo


文章目录

  • 一、引言
  • 二、SPI是什么
  • 三、使用介绍
  • 四、原理介绍
  • 1、SPI注解
  • 五、源码剖析
  • 1、Loader的创建
  • 2、获取实例
  • 2.1 解析文件配置
  • 2.2 实例化创建
  • 2.3 前置处理
  • 2.4 依赖注入
  • 2.5 后置操作
  • 2.6 Wrapper 的包装
  • 2.6.1 Wrapper缓存
  • 2.6.2 Wrapper实现
  • 2.7 生命周期管理
  • 六、流程图
  • 七、总结


一、引言

兄弟们,上次的故障结果出来了

还好销售团队给力,没有让客户几千万的单子丢掉,成功挽回了本次损失


趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了_java_02

不过内部处罚还是相对严重,年终奖悬了

趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了_dubbo_03

这也告诫我们 要对生产保持敬畏之情!

恰巧最近领导看我在写 Dubbo 源码系列,看到我们的项目中用了 SPI 扩展

于是给我一个将功补过的机会,让我好好的分析分析 DubboSPI 的扩展机制,进行组内技术分享

作为一个常年分享 源码系列 文章的选手,当然不会拒绝!

乾坤未定,你我皆是黑马,冲!

二、SPI是什么

SPI 全称 Service Provider Interface ,是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。

趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了_dubbo_04

Java SPI 实际上是 基于接口的编程+策略模式+配置文件 组合实现的动态加载机制。

Java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。

将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

所以 SPI 的核心思想就是解耦

三、使用介绍

我们定义一个接口:City

@SPI
public interface City {
    String getCityName();
}

实现其两个类:

  • BeijingCity
public class BeijingCity implements City{
    @Override
    public String getCityName() {
        return "北京";
    }
}
  • TianjinCity
public class TianjinCity implements City{
    @Override
    public String getCityName() {
        return "天津";
    }
}

重点来了:我们要在 resources 文件夹下面建立一个路径:META-INF/dubbo

然后我们建立一个 txt 名为:com.dubbo.provider.SPI.Dubbo.City,如下:

我们在这个文件中写上各实现类的路径:

beijing=com.dubbo.provider.SPI.Dubbo.BeijingCity
tianjin=com.dubbo.provider.SPI.Dubbo.TianjinCity

有的朋友可能会问,这里为什么和 Java SPI 的实现不同?

这也正是 Dubbo 实现精准实例化的原因,我们后面也会聊到

测试方法:

public class DubboSPITest {
    public static void main(String[] args) {
        ExtensionLoader<City> loader = ExtensionLoader.getExtensionLoader(City.class);
        City tianjin = loader.getExtension("beijing");
        System.out.println(tianjin.getCityName());
    }
}

测试结果:

北京

从这里我们可以看出,Dubbo 可以通过 loader.getExtension("beijing") 精确的生成我们需要的实例

精确生成是如何实现的呢?我们继续往下看

四、原理介绍

在源码介绍之前,我们先说几个原理细节,防止大家后面的源码看迷糊

1、SPI注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    /**
     * default extension name
     */
    String value() default "";

    /**
     * scope of SPI, default value is application scope.
     */
    ExtensionScope scope() default ExtensionScope.APPLICATION;
}

SPI 注解中,存在两个参数:valuescope

value

  • 作用:如果某个 SPI 扩展没有指定实现类名称,则会使用 @SPI 注解中指定的默认值

scope:指定 SPI 扩展实现类的作用域( Constants.SINGLETON

  • Constants.FRAMEWORK(框架作用域):实现类在 Dubbo 框架中只会创建一个实例,并且在整个应用程序中共享。
  • Constants.APPLICATION(应用程序作用域):实现类在应用程序上下文中只会创建一个实例,并且在整个应用程序中共享。
  • Constants.MODULE(模块作用域):实现类在模块上下文中只会创建一个实例,并且在整个模块中共享。
  • Constants.SELF(自定义作用域):实现类的作用范围由用户自行定义,可以是任何范围。

当然,这里 Dubbo 默认的是 Constants.APPLICATION,我们也只需要关注这个即可。

五、源码剖析

1、Loader的创建

我们 DubboSPIExtensionLoader.getExtensionLoader(City.class) 开始,看一看其实现方案

public <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    // 1、校验
    checkDestroyed();

    // 2、是否有本地Loader缓存
    ExtensionLoader<T> loader = (ExtensionLoader<T>) extensionLoadersMap.get(type);

    // 3、是否有本地Scope缓存
    ExtensionScope scope = extensionScopeMap.get(type);
    
    // 4、如果当前的Scope为空
    // 4.1 获取当前接口类的SPI注解
    // 4.2 获取当前注解的scope
    // 4.3 放入scope缓存
    if (scope == null) {
        SPI annotation = type.getAnnotation(SPI.class);
        scope = annotation.scope();
        extensionScopeMap.put(type, scope);
    }

    // 5、如果加载器为空且当前是SELF,直接创建loader
    if (loader == null && scope == ExtensionScope.SELF) {
        loader = createExtensionLoader0(type);
    }

    // 6、如果当前加载器为空,去父类找加载器
    if (loader == null) {
        if (this.parent != null) {
            loader = this.parent.getExtensionLoader(type);
        }
    }

    // 7、如果父类也没有实例化,那么实例化并放入缓存
    if (loader == null) {
        loader = createExtensionLoader(type);
    }
	
    // 8、返回加载器
    return loader;
}

从上面的源码我们可以看到,获取 ExtensionLoader 采用了 缓存 + 父类继承 的模式

这种继承机制设计得比较巧妙,可以避免重复加载类,提高系统性能。

2、获取实例

Dubbo 通过 loader.getExtension("tianjin") 获取对应的实例

public T getExtension(String name) {
    T extension = getExtension(name, true);
    return extension;
}

public T getExtension(String name, boolean wrap) {
    // 1、校验
    checkDestroyed();
    
    // 2、参数为true,表明采用默认的实现类
    // 2.1 我们上面SPI中的value参数,若指定tianjin,则采用tianjin的实现类
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    
    String cacheKey = name;
    if (!wrap) {
        cacheKey += "_origin";
    }
    // 3、查看当前缓存中是否含有该实例
    // 3.1 如果当前的cacheKey没有Holder的话,创建一个
    final Holder<Object> holder = getOrCreateHolder(cacheKey);
    
    // 4、如果实例为空,采用DCL机制创建实例
    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 Holder<Object> getOrCreateHolder(String name) {
    // 1、获取当前name的Holder
    Holder<Object> holder = cachedInstances.get(name);
    // 2、没有则创建并扔进缓存
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<>());
        holder = cachedInstances.get(name);
    }
    // 3、返回
    return holder;
}

Holder 类是一个简单的容器类,用于保存某个对象的引用

DubboExtensionLoader 类中,Holder 类被用于实现对 SPI 扩展实现类的缓存

Holder 结构如下:

public class Holder<T> {
    private volatile T value;
    public void set(T value) {
        this.value = value;
    }
    public T get() {
        return value;
    }
}

我们创建实例一共有以下几部分:

  • 解析文件配置得到对应的类
  • 通过实例化创建相关的类
  • 初始化之前前置操作
  • 依赖注入
  • 初始化之后后置操作
  • Wrapper 的包装
  • 是否具有生命周期管理的能力

我们挨个的讲解

2.1 解析文件配置

Class<?> clazz = getExtensionClasses().get(name);

private Map<String, Class<?>> getExtensionClasses() {
    // 1、从缓存中获取类的信息
    Map<String, Class<?>> classes = cachedClasses.get();
    
    // 2、DCL创建(经典的单例设计模式)
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 3、加载类信息并放至缓存中
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {
    // 1、校验
    checkDestroyed();
    
    // 2、是否有默认的类
    // 2.1 我们之前聊过的SPI注解的value机制
    cacheDefaultExtensionName();
	  
    // 3、这里有三个文件解析器
    // 3.1 DubboInternalLoadingStrategy:解析META-INF/dubbo/internal/
    // 3.2 DubboLoadingStrategy:解析META-INF/dubbo/
    // 3.3 ServicesLoadingStrategy:解析META-INF/services/
    // 3.4 解析文件并放至缓存
    Map<String, Class<?>> extensionClasses = new HashMap<>();
    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy, type.getName());
		
        if (this.type == ExtensionInjector.class) {
            loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
        }
    }
	
    // tianjin:"class com.msb.dubbo.provider.SPI.Dubbo.TianjinCity"
    // beijing:"class com.msb.dubbo.provider.SPI.Dubbo.BeijingCity"
    return extensionClasses;
}

2.2 实例化创建

// 1、从缓存中获取
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
    // 2、缓存为空则创建并放至缓存
    extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
    instance = (T) extensionInstances.get(clazz);
}

// 1、获取当前类的所有的构造方法
// 2、判断是否有符合的构造方法,若没有则报错
// 3、有符合的构造犯法,返回即可
private Object createExtensionInstance(Class<?> type) throws ReflectiveOperationException {
    return instantiationStrategy.instantiate(type);
}

2.3 前置处理

类似 Spirng 的前置处理器,之前也说过,感兴趣的可以看一下,整体思路区别不大

instance = postProcessBeforeInitialization(instance, name);

private T postProcessBeforeInitialization(T instance, String name) throws Exception {
    if (extensionPostProcessors != null) {
        for (ExtensionPostProcessor processor : extensionPostProcessors) {
            instance = (T) processor.postProcessBeforeInitialization(instance, name);
        }
    }
    return instance;
}

2.4 依赖注入

  • 首先,如果依赖注入器为 null,则直接返回传入的实例。
  • 然后,遍历传入实例的所有方法,找到所有的 setter 方法。
  • 对于每个 setter 方法,如果标注了 @DisableInject 注解,则跳过该方法,不进行注入。
  • 如果 setter 方法的参数类型是基本类型,则跳过该方法,不进行注入。
  • 如果 setter 方法的参数类型不是基本类型,则尝试从依赖注入器中获取该类型对应的实例,并调用该 setter 方法进行注入。
  • 如果获取实例失败,则记录错误日志。
  • 最后,返回注入后的实例。
injectExtension(instance);

private T injectExtension(T instance) {
    for (Method method : instance.getClass().getMethods()) {
        // 1、如果不是setter方法,直接跳过
        if (!isSetter(method)) {
            continue;
        }
        
        // 2、包含了DisableInject注解,直接跳过
        if (method.isAnnotationPresent(DisableInject.class)) {
                continue;
            }

            
            if (method.getDeclaringClass() == ScopeModelAware.class) {
                continue;
            }
        	
        	// 3、如果是基本数据类型,跳过
            if (instance instanceof ScopeModelAware || instance instanceof ExtensionAccessorAware) {
                if (ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method))) {
                    continue;
                }
            }
            Class<?> pt = method.getParameterTypes()[0];
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                // 4、依赖注入器中获取该类型对应的实例,并调用该 setter 方法进行注入
                // 4.1 这里直接拿取的ListableBeanFactory->DefaultListableBeanFactory
                String property = getSetterProperty(method);
                Object object = injector.getInstance(pt, property);
                
                // 5、将当前的对象注入到实例里面
                if (object != null) {
                    method.invoke(instance, object);
                }
            }
    return instance;
}

2.5 后置操作

  • 类似 Spirng 的后置处理器,之前也说过,感兴趣的可以看一下,整体思路区别不大
instance = postProcessAfterInitialization(instance, name);

private T postProcessAfterInitialization(T instance, String name) throws Exception {
    if (instance instanceof ExtensionAccessorAware) {
        ((ExtensionAccessorAware) instance).setExtensionAccessor(extensionDirector);
    }
    if (extensionPostProcessors != null) {
        for (ExtensionPostProcessor processor : extensionPostProcessors) {
            instance = (T) processor.postProcessAfterInitialization(instance, name);
        }
    }
    return instance;
}

2.6 Wrapper 的包装

2.6.1 Wrapper缓存

在讲该部分之前,我们先来看 cachedWrapperClasses 这个缓存的来历:

在我们上面解析文件配置时,会进行 loadClass,这里不仅会解析正常的类,也会解析 Wrapper 类,方便后面的包装

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,boolean overridden) {
    if (isWrapperClass(clazz)) {
        cacheWrapperClass(clazz);
    }
}

从这里我们可以看到,最关键的当属判断当前的 Class 是不是属于 WrapperClass

protected boolean isWrapperClass(Class<?> clazz) {
    // 1、获取构造方法
    Constructor<?>[] constructors = clazz.getConstructors();
    // 2、从构造方法中取出参数为 1 且类型等于当前接口的
    for (Constructor<?> constructor : constructors) {
        if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0] == type) {
            return true;
        }
    }
    return false;
}

而具体的实现如下:

public class CityWrapper implements City{

    private City city;
    // 怎样判断扩展点还是aop切面呢?
    // 通过是否有这样的一个构造方法来判断
    public CityWrapper(City city) {
        this.city = city;
    }

    @Override
    public String getCityName() {
        return "文明城市" + city.getCityName();
    }
}

了解这个之后,我们再来看看 Dubbo 如何处理这些类似 AOP 的包装

2.6.2 Wrapper实现
if (wrap) {
    List<Class<?>> wrapperClassesList = new ArrayList<>();
	// 1、判断是否有Wrapper缓存
    // 1.1 将缓存放入当前
    // 1.2 排序 + 翻转
    if (cachedWrapperClasses != null) {
        wrapperClassesList.addAll(cachedWrapperClasses);
        wrapperClassesList.sort(WrapperComparator.COMPARATOR);
        Collections.reverse(wrapperClassesList);
    }
	
    // 2、当前的wrapper缓存不为空
    if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
        // 循环包装
        for (Class<?> wrapperClass : wrapperClassesList) {
            // 3、获取Wrapper注解,是否需要包装(正常都是包装的)
            Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
            // 4、判断下是否包装条件
            boolean match = (wrapper == null) ||
                ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
                    !ArrayUtils.contains(wrapper.mismatches(), name));
            // 5、符合包装
            // 5.1 将当前类封装至wrapper中
            // 5.2 做一些后置处理
            if (match) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                instance = postProcessAfterInitialization(instance, name);
            }
        }
    }
}

通过这种方式,我们可以创建不同的 wrapper,实现 AOP 的作用

读过 从源码全面解析 dubbo 服务端服务调用的来龙去脉从源码全面解析 dubbo 消费端服务调用的来龙去脉 的文章,这时候应该理解最后的那些 过滤器 怎么实现的了

比如,我们现在有两个 wrapper 类,分别是 CityWrapperCityWrapper2,实现类是 TianjinCity

那么,我们最终 TianjinCity 返回的实例如下:

  • CityWrapper
  • CityWrapper2
  • TianjinCity

趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了_后端_05

不得不说,这个包装还是有点秀秀的

2.7 生命周期管理

  • 实现 Lifecycle 的接口
initExtension(instance);

六、流程图

高清图片私聊博主获取

趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了_面试_06

七、总结

鲁迅先生曾说:独行难,众行易,和志同道合的人一起进步。彼此毫无保留的分享经验,才是对抗互联网寒冬的最佳选择。


好博客就要一起分享哦!分享海报

此处可发布评论

评论(0展开评论

暂无评论,快来写一下吧

展开评论

您可能感兴趣的博客

客服QQ 1913284695