浅谈Dubbo之SPI机制

什么是SPI

从面向对象的角度来说,接口不应依赖与实现。就拿操作数据库来说,每种数据库的操作方式都是不同的,如果更换了数据库,那么我们的代码就要推翻重来,JDBC就是一套规范,定义了操作方式的基本接口,各数据库厂商去做具体的实现,这样程序就不依赖底层的实现。但是我们在编写程序的时候,怎么知道需要使用的是哪种数据库呢?这里就要说到JAVA中提到的SPI(Service Provider Interface),是JDK内置的一种动态服替换发现服务实现者机制。

JAVA实现SPI需要如下步骤:

(1)编写接口并实现

public interface Cmand {
    public void execute();
}
public class ShutdownCommand implements Cmand {
    public void execute() {
        System.out.println("shutdown....");  
    }
}
public class StartCommand implements Cmand {
    public void execute() {
        System.out.println("start....");
    }
}
public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<Cmand> loader = ServiceLoader.load(Cmand.class);
        System.out.println(loader);
 
        for (Cmand Cmand : loader) {
            Cmand.execute();
        }
    }
}

(2)在下创建文件**META-INF/services/com.unei.serviceloader.Cmand**

com.unei.serviceloader.impl.ShutdownCommand  
com.unei.serviceloader.impl.StartCommand 

(3)使用ServiceLoader加载并执行。

public class SPIMain {
    public static void main(String[] args) {
        ServiceLoader<Cmand> loader = ServiceLoader.load(Cmand.class);
        System.out.println(loader);
 
        for (Cmand Cmand : loader) {
            Cmand.execute();
        }
    }

优点:使用 Java SPI 机制的优势是实现解耦,使得接口的定义与具体业务实现分离,而不是耦合在一起。应用进程可以根据实际业务情况启用或替换具体组件。

缺点:

(1)不能按需加载。虽然 ServiceLoader 做了延迟载入,但是基本只能通过遍历全部获取,也就是接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费

(2)获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类

(3)多个并发多线程使用 ServiceLoader 类的实例是不安全的

(4)加载不到实现类时抛出并不是真正原因的异常,错误很难定位。

Dubbo SPI

与JAVA SPI相比,Dubbo SPI做了一些改进和优化:

(1)JDK标准SPI会一次性实例化所有扩展点,如果没有用上也去加载,则浪费资源。

(2)如果扩展加载失败,则连接的名称都获取不到了。由于异常被捕获,导致问题很难排查。

(3)增加了对IOC和AOP的支持,一个扩展可以直接setter注入其他属性,并且可以做到按需加载。

下面就来聊聊Dubbo的SPI是如何使用的,我们从ExtensionLoader开始讲起。

ExtensionLoader

org.apache.dubbo.common.extension.ExtensionLoader#getExtensionLoader

通过getExtensionLoader获得一个ExtensionLoader实例,其中有三个方法:

  • getActivateExtension:根据条件获取当前扩展可自动激活的实现
  • getExtension:根据指定的名称获取实现
  • getAdaptiveExtension:获取当前扩展的自适应实现
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() + "!");
        }
				// 尝试从缓存中获取  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;
    }

加载器会先尝试从缓存中获取,缓存中没有的话会去 创建一个 ExtensionLoader。

private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

在构造函数中有两个参数,type是加载SPI 的Class,objectFactory 是扩展的工厂。ExtensionFactory 也是基于SPI创建的,接下来我们看 getAdaptiveExtension() 是或者获取到一个适配的ExtensionFactory,这就要发挥 Adaptive注解的作用了。

Adaptive

public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();  // (1)
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();  // (2)
                    if (instance == null) {
                        try {
                            instance = createAdaptiveExtension(); // (3)
                            cachedAdaptiveInstance.set(instance);  // (4)
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }
(1)、(2)重试从缓存中获取
(3) 创建一个ExtensionFactory
(4)将创建的ExtensionFactory放到缓存中

我们重点看下**createAdaptiveExtension()**方法。

- createAdaptiveExtension()  // (1)
  - injectExtension()        // (2)
    - getAdaptiveExtensionClass()    // (3)
      -getExtensionClasses()    //(4)
        -loadExtensionClasses()   // (5)
      -createAdaptiveExtensionClass()   //(6)

先看第三步,尝试获取自适应的ExtensionFactory,先尝试去获取有Adaptive注解的ExtensionFactory,获取不到的话会从第六步创建一个代理的Adaptive

private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        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;
    }

这一段代码就是加载META-INF/services、META-INF/dubbo/等目录下的文件。

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.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);  // (1)
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);  //(2)
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                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)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }

我们可以看到(1)如果加了Adaptive的会打上特殊标记,方便后期获取自适应的类。

(2)cacheWrapperClass是Dubbo中的AOP,提供了一种包装,是解耦先Wrapper需要如下规范:

  • 该类要实现SPI接口
  • 该类中要有SPI接口的引用
  • 该类中必须含有一个含参的构造方法且参数只能有一个类型为SPI接口
  • 在接口实现方法中要调用 SPI 接口引用对象的相应方法
  • 该类名称以 Wrapper 结尾
  • 只能有一个Wapper

加载的类中如果没有Adaptive的话,会调用createAdaptiveExtensionClass生产一个Adaptive。

private Class<?> createAdaptiveExtensionClass() {
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

这段代码是通过代理实现一个Adaptive类,在SPI接口中,有一个方法上必须有@Adaptive注解,并且方法中含有一个URL参数,在URL参数中能获取一个实现。我们看下下面的SPI接口如何生成一个自适应Adaptive

@SPI("impl1")
public interface AddExt3 {
    @Adaptive
    String echo(URL url, String s);
}



public class AddExt3_ManualAdaptive implements AddExt3 {
    public String echo(URL url, String s) {
        AddExt3 addExt1 = ExtensionLoader.getExtensionLoader(AddExt3.class).getExtension(url.getParameter("add.ext3"));
        return addExt1.echo(url, s);
    }
}
package org.apache.dubbo.common.extension.ext8_add;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class AddExt3$Adaptive implements org.apache.dubbo.common.extension.ext8_add.AddExt3 {
    public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1)  {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("add.ext3", "impl1");
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext8_add.AddExt3) name from url (" + url.toString() + ") use keys([add.ext3])");
        org.apache.dubbo.common.extension.ext8_add.AddExt3 extension = (org.apache.dubbo.common.extension.ext8_add.AddExt3)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext8_add.AddExt3.class).getExtension(extName);
        return extension.echo(arg0, arg1);
    }
}

为接口中每个有@Adaptive注解的方法生成默认实现(没有注解的方法生成空实现),每个默认实现都会从URL中提取Adaptive参数值,并以此为依据动态加载扩展点。然后框架会使用不同的编译器,把实现类字符串编译为自适应类并返回。生成代码逻辑如下:

(1)生成package、import、类名等信息,其中import只会导入ExtensionLoader,其他类都是使用全路径方式。类名为”接口名 + $Adaptive“.

(2) 遍历接口中所有方法,获取方法的返回类型、参数等信息。

(3)生成参数校验代码。

(4)生成默认实现类名称。如果@Adaptive注解中没有默认值,则根据类名生成,如果类名为YyXx,则实现为yy.xx。

(5)生成获取具体扩展实现类。最终还是调用getExtension(extName)来获取自适应扩展类的真正实现。

(6)调用目标实现的代码。

Activate

此注解要从**getActivateExtension(URL url, String key, String group)**方法讲起,key是扩展点的key,group为激活的组,可以获取所有自动激活扩展点。流程如下:

public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> exts = new ArrayList<>();
        List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            getExtensionClasses();  // (1)
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Object activate = entry.getValue();

                String[] activateGroup, activateValue;

                if (activate instanceof Activate) {   // (2)
                    activateGroup = ((Activate) activate).group();  
                    activateValue = ((Activate) activate).value();  
                } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                    activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                    activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
                } else {
                    continue;
                }
                if (isMatchGroup(group, activateGroup)  // (3)
                        && !names.contains(name)
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) {
                    exts.add(getExtension(name));
                }
            }
            exts.sort(ActivateComparator.COMPARATOR);   // (4)
        }
        List<T> usrs = new ArrayList<>();
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                if (DEFAULT_KEY.equals(name)) {  // (5)
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    usrs.add(getExtension(name));   // (6)
                }
            }
        }
        if (!usrs.isEmpty()) {
            exts.addAll(usrs); // (7)
        }
        return exts;
    }

(1)从文件中加载初始化所有扩展类实现的集合。

(2)遍历所有含有@Activate注解集合,获取activateGroup、activateValue激活的组合value信息。

(3)根据URL匹配条件,获得所有符合激活条件的扩展类实现,并加入集合中。

(4)根据@Activate中配置的order参数排序。

(3)遍历用户自定义扩展类名称,根据URL配置顺序,调整扩展点激活顺序,如URL为:test://localhost/test/ext=order1,default,则激活的顺序为order1、default。如何扩展名含有”-“则不会被激活。default代表激活所有的扩展点。

(4)返回所有激活类的集合。

ExtensionFactory

上面说的ExtensionLoader是SPI的核心,但是ExtensionFactory又是怎么创建出来的呢?ExtensionFactory是基于ExtensionFactory创建的,这个工厂接口也有SPI注解。

@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);
}

ExtensionFactory接口有多个实现,AdaptiveExtensionFactory这个实现上有@Adaptive注解,所以他会作为默认实现。AdaptiveExtensionFactory在构造函数中初始化所有实现到factories集合中,在getExtension的时候获取一个实现。

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

SpringExtensionFactory如何与Spring整合

public class SpringExtensionFactory implements ExtensionFactory {
   
    private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();
    private static final ApplicationListener SHUTDOWN_HOOK_LISTENER = new ShutdownHookListener();

		// (1)
    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) context).registerShutdownHook();
            DubboShutdownHook.getDubboShutdownHook().unregister();
        }
        BeanFactoryUtils.addApplicationListener(context, SHUTDOWN_HOOK_LISTENER);
    }
 		
    public <T> T getExtension(Class<T> type, String name) {
    		···
				// (2)
        for (ApplicationContext context : CONTEXTS) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }

        if (Object.class == type) {
            return null;
        }
				// (3)
        for (ApplicationContext context : CONTEXTS) {
          ···
            return context.getBean(type);
       		 ···
        }
        return null;
    }

(1) 在ReferenceBean和ServiceBean初始化的时候保存Spring上下文

(2)遍历所有Spring上下文,现根据名字重容器中查找

(3)名字没有查询到再根据类型查询

SpiExtensionFactory

SpiExtensionFactory主要就是获取扩展点接口对应的Adaptive实现类。如某个扩展点实现类ClassA有@Adaptive注解,则调用SpiExtensionFactory#getExtension直接返回ClassA实例。代码如下:

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

总结

本文简单分别介绍了 Java SPI 与 Dubbo SPI 用法,并对 Dubbo SPI 的加载拓展类的过程进行了分析。同时分析了Dubbo AOP的实现原理。如果文章中有错误不妥之处,希望大家指正。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值