美文网首页
Dubbo系列--SPI的实现原理《一》

Dubbo系列--SPI的实现原理《一》

作者: Teddy_b | 来源:发表于2023-11-21 15:04 被阅读0次

    在Dubbo的源码中存在大量的SPI代码,Dubbo用这种方式来获取接口的实现类,几个好处:

    • 面向接口编程

    • 代码扩展性好,可以自定义的点很多

    • 类对象在真正需要的时候再创建

    主要的实现都在ExtensionLoader这个类中,每个接口都会对应一个ExtensionLoader对象,用于加载这个接口的实现类

    @SPI(value = "dubbo", scope = ExtensionScope.FRAMEWORK)
    public interface Protocol {
    ...
    }
    

    需要通过SPI方式加载的接口,需要用注解@SPI标志,value对应的是默认值,scope对应的是这个接口的作用域,这里可以先忽略

    SPI流程

    加载Class

    Dubbo中存在三种LoadingStrategy,主要区别是加载的目录不同,我们主要看下这个实现类

    public class DubboInternalLoadingStrategy implements LoadingStrategy {
    
        @Override
        public String directory() {
            return "META-INF/dubbo/internal/";
        }
        ...
    }
    

    这个加载的是META-INF/dubbo/internal/目录下的文件:

    image.png

    如需要加载Dubbo中有哪些协议Protocol的实现类,那么这里type=org.apache.dubbo.rpc.Protocol,加载的就是这个org.apache.dubbo.rpc.Protocol文件

    通过类加载器加载项目中所有的META-INF/dubbo/internal/目录下的这个org.apache.dubbo.rpc.Protocol文件(包括jar包)

    private void loadDirectoryInternal(Map<String, Class<?>> extensionClasses,
                                           LoadingStrategy loadingStrategy, String type)
            throws InterruptedException {
            String fileName = loadingStrategy.directory() + type;
            try {
                ...
    
                Map<ClassLoader, Set<java.net.URL>> resources = ClassLoaderResourceLoader.loadResources(
                    fileName, classLoadersToLoad);
                resources.forEach(((classLoader, urls) -> {
                    loadFromClass(extensionClasses, loadingStrategy.overridden(), urls, classLoader,
                        loadingStrategy.includedPackages(), loadingStrategy.excludedPackages(),
                        loadingStrategy.onlyExtensionClassLoaderPackages());
                }));
            }
          ...
        }
    

    Dubbo内置了这些实现类

    filter=org.apache.dubbo.rpc.cluster.filter.ProtocolFilterWrapper
    qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
    registry=org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol
    service-discovery-registry=org.apache.dubbo.registry.integration.RegistryProtocol
    listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
    mock=org.apache.dubbo.rpc.support.MockProtocol
    serializationwrapper=org.apache.dubbo.rpc.protocol.ProtocolSerializationWrapper
    securitywrapper=org.apache.dubbo.rpc.protocol.ProtocolSecurityWrapper
    invokercount=org.apache.dubbo.rpc.protocol.InvokerCountWrapper
    
    dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
    injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
    rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
    tri=org.apache.dubbo.rpc.protocol.tri.TripleProtocol
    grpc=org.apache.dubbo.rpc.protocol.tri.GrpcProtocol
    

    这样就可以把这些实现类的Class对象加载到内存中进行缓存了

    实例化Class

    在需要实例化的时候

    private T createExtension(String name, boolean wrap) {
            Class<?> clazz = getExtensionClasses().get(name);
            ...
            try {
                T instance = (T) extensionInstances.get(clazz);
                if (instance == null) {
                    extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
                    instance = (T) extensionInstances.get(clazz);
                    instance = postProcessBeforeInitialization(instance, name);
                    injectExtension(instance);
                    instance = postProcessAfterInitialization(instance, name);
                }
    
                ...
                initExtension(instance);
                return instance;
            } catch (Throwable t) {
                throw new IllegalStateException(
                    "Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(),
                    t);
            }
        }
    

    实例化分为几步:

    • 反射调用构造器创建对象

    • 前置处理postProcessBeforeInitialization,目前为空

    • 属性注入injectExtension,此时会遍历所有的set方法,由set方法推导出属性名称和属性类型,然后再通过SPI方式获取这个类型的实现类,再注入到属性中

    • 后置处理postProcessAfterInitialization,主要是注入Dubbo的模块属性

    • 初始化initExtension,如果实现了Lifecycle接口,调用initialize()方法

    特殊的实例化

    Adaptive

    ExtensionLoader中可以通过这个方法获取接口的Adaptive类

    private Class<?> getAdaptiveExtensionClass() {
            getExtensionClasses();
            if (cachedAdaptiveClass != null) {
                return cachedAdaptiveClass;
            }
            return cachedAdaptiveClass = createAdaptiveExtensionClass();
        }
    

    对应着两种加载方式:

    • 通过再实现类上指定@Adaptive注解:如加载org.apache.dubbo.common.extension.ExtensionInjector的Adaptive类

      @Adaptive
      public class AdaptiveExtensionInjector implements ExtensionInjector, Lifecycle {
      ...
      }
      
    • 所有实现类中均不存在@Adaptive注解时,动态生成一个实现类作为Adaptive类;此时需要接口中存在@Adaptive注解的方法,并且方法的入参中直接或者间接存在URL对象,如加载org.apache.dubbo.rpc.Protocol的Adaptive类,由于不存在@Adaptive注解类,因此会动态生成一个Adaptive类:

    package org.apache.dubbo.rpc;
    
    import org.apache.dubbo.rpc.model.ScopeModel;
    import org.apache.dubbo.rpc.model.ScopeModelUtil;
    
    public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    
              public void destroy()  {
                        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface         org.apache.dubbo.rpc.Protocol is not adaptive method!");
              }
    
              public int getDefaultPort()  {
                        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
              }
    
              public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
                          if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
                          if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
                          org.apache.dubbo.common.URL url = arg0.getUrl();
                          String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
                          if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
                           ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.rpc.Protocol.class);
                           org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)scopeModel
                                                                       .getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
                           return extension.export(arg0);
                }
    
                public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
                            if (arg1 == null) throw new IllegalArgumentException("url == null");
                            org.apache.dubbo.common.URL url = arg1;
                            String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
                            if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
                            ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.rpc.Protocol.class);
                            org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)scopeModel
                                                                      .getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
                            return extension.refer(arg0, arg1);
              }
    
              public java.util.List getServers()  {
                            throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
              }
    }
    

    可以看到这个动态生成的Adaptive类只是一个代理类,就是根据URL中指定的协议名称、或者默认实现,代理到协议对应的具体实现类或者默认实现

    再如ProxyFactory生成的Adaptive类

    package org.apache.dubbo.rpc;
    
    import org.apache.dubbo.rpc.model.ScopeModel;
    import org.apache.dubbo.rpc.model.ScopeModelUtil;
    
    public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
    
    
             public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
                    if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
                    if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
                    org.apache.dubbo.common.URL url = arg0.getUrl();
                    String extName = url.getParameter("proxy", "javassist");
                    if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
                    ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.rpc.ProxyFactory.class);
                    org.apache.dubbo.rpc.ProxyFactory extension =  (org.apache.dubbo.rpc.ProxyFactory)scopeModel
                                                                  .getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
                    return extension.getProxy(arg0);
              }
    
    
              public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException {
                      if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
                      if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
                      org.apache.dubbo.common.URL url = arg0.getUrl();
                      String extName = url.getParameter("proxy", "javassist");
                      if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
                      ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.rpc.ProxyFactory.class);
                      org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)scopeModel
                                                                     .getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
                      return extension.getProxy(arg0, arg1);
              }
                
    
                public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
                        if (arg2 == null) throw new IllegalArgumentException("url == null");
                        org.apache.dubbo.common.URL url = arg2;
                        String extName = url.getParameter("proxy", "javassist");
                        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])");
                        ScopeModel scopeModel = ScopeModelUtil.getOrDefault(url.getScopeModel(), org.apache.dubbo.rpc.ProxyFactory.class);
                        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)scopeModel
                                                                       .getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
                        return extension.getInvoker(arg0, arg1, arg2);
                }
    }
    

    生成的模式基本上都是一样的,我们可以自己实现相应的接口,然后加上@Adaptive注解,那么就会优先加载我们自己的实现类;

    如果不存在Adaptive类,那么将会自动生成上述的代理类,来执行Dubbo默认的实现类,如ProxyFactory的默认实现是JavassistProxyFactory;

    Protocol的默认实现是根据协议而来的,协议不同时会对应不同的默认实现,如果没有指定协议时,则协议默认是dubbo,那么对应的实现类就是DubboProtocol

    Wrapper

    Wrapper顾名思义就是再对象外面再包装一层

    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) extensionInstances.get(clazz);
                if (instance == null) {
                    extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
                    instance = (T) extensionInstances.get(clazz);
                    instance = postProcessBeforeInitialization(instance, name);
                    injectExtension(instance);
                    instance = postProcessAfterInitialization(instance, name);
                }
    
               
                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);
                            boolean match = (wrapper == null) || ((ArrayUtils.isEmpty(
                                wrapper.matches()) || ArrayUtils.contains(wrapper.matches(),
                                name)) && !ArrayUtils.contains(wrapper.mismatches(), name));
                            if (match) {
                                instance = injectExtension(
                                    (T) wrapperClass.getConstructor(type).newInstance(instance));
                                instance = postProcessAfterInitialization(instance, name);
                            }
                        }
                    }
                }
    
                // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
                initExtension(instance);
                return instance;
            } catch (Throwable t) {
                throw new IllegalStateException(
                    "Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(),
                    t);
            }
        }
    
    

    相比普通的实例化过程,Wrapper对象的实例化过程就多了一步指定wrap=true

    再SPI方式加载实现类的时候,会特殊处理这种实现类

    protected boolean isWrapperClass(Class<?> clazz) {
            Constructor<?>[] constructors = clazz.getConstructors();
            for (Constructor<?> constructor : constructors) {
                if (constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0] == type) {
                    return true;
                }
            }
            return false;
        }
    

    这种实现类存在一个构造器,构造器只有一个入参,并且参数类型就是这个接口类型(类比下Java中的IO类),看个具体的例子:

    public class ProtocolFilterWrapper implements Protocol {
        private final Protocol protocol;
    
        public ProtocolFilterWrapper(Protocol protocol) {
            if (protocol == null) {
                throw new IllegalArgumentException("protocol == null");
            }
            this.protocol = protocol;
        }
    }
    

    这个org.apache.dubbo.rpc.Protocol的实现类就有一个构造函数,它只有一个入参,并且类型就是Protocol,这种就会被识别为Wrapper类,并加入到wrapperClassesList这个缓存中,用于对具体的对象进行包装,如果包装类比较多的话,会进行简单的排序,然后形成这种大肠包小肠的效果

    image.png

    BeanFactory

    Dubbo中除了通过SPI方式获取对象,还有一种方式,是通过BeanFactory获取对象,这一点和Spring中的类似

    再Dubbo初始化的时候,就会创建一个BeanFactory

    protected void initialize() {
            synchronized (instLock) {
                this.extensionDirector = new ExtensionDirector(parent != null ? parent.getExtensionDirector() : null, scope, this);
                this.extensionDirector.addExtensionPostProcessor(new ScopeModelAwareExtensionProcessor(this));
                this.beanFactory = new ScopeBeanFactory(parent != null ? parent.getBeanFactory() : null, extensionDirector);
    
                // Add Framework's ClassLoader by default
                ClassLoader dubboClassLoader = ScopeModel.class.getClassLoader();
                if (dubboClassLoader != null) {
                    this.addClassLoader(dubboClassLoader);
                }
            }
        }
    

    然后通过一系列的Initializer再初始化的时候往BeanFactory中注册Bean,具体包括哪些Initializer呢?也是通过SPI方式去加载实现类的,如:

    public class Fastjson2ScopeModelInitializer implements ScopeModelInitializer {
    
        @Override
        public void initializeFrameworkModel(FrameworkModel frameworkModel) {
            ScopeBeanFactory beanFactory = frameworkModel.getBeanFactory();
            beanFactory.registerBean(Fastjson2CreatorManager.class);
            beanFactory.registerBean(Fastjson2SecurityManager.class);
        }
    }
    
    注册Bean
    public <T> T getOrRegisterBean(String name, Class<T> type) {
            T bean = getBean(name, type);
            if (bean == null) {
                // lock by type
                synchronized (type) {
                    bean = getBean(name, type);
                    if (bean == null) {
                        bean = createAndRegisterBean(name, type);
                    }
                }
            }
            registeredClasses.add(type);
            return bean;
        }
    
    private <T> T createAndRegisterBean(String name, Class<T> clazz) {
            checkDestroyed();
            T instance = getBean(name, clazz);
            if (instance != null) {
                throw new ScopeBeanException("already exists bean with same name and type, name=" + name + ", type=" + clazz.getName());
            }
            try {
                instance = instantiationStrategy.instantiate(clazz);
            } catch (Throwable e) {
                throw new ScopeBeanException("create bean instance failed, type=" + clazz.getName(), e);
            }
            registerBean(name, instance);
            return instance;
        }
    

    注册Bean包括几步:

    • 通过双重判断方式同步的获取Bean,确保不会重复创建Bean

    • Bean不存在的时候通过反射创建一个Bean

    • 将Bean加入到缓存

    • 初始化一些Dubbo的模块及SPI获取方式等基础属性,其它属性不会进行初始化,把SPI方式都给你了,需要啥自己去通过SPI方式获取吧,授人以鱼不如授人以渔

    总结

    再Dubbo中这两种获取对象的方式很多,也是Dubbo中最主要的两种获取对象的方式了,留个印象对理解Dubbo有好处

    参考

    相关文章

      网友评论

          本文标题:Dubbo系列--SPI的实现原理《一》

          本文链接:https://www.haomeiwen.com/subject/ztpswdtx.html