dubbo-spi机制

本文适合对dubbo熟练使用的人阅读,需要了解dubbo的相关基础知识。

文中主要是介绍dubbo-SPI的机制,将从以下几个方面进行介绍
1、dubbo框架分层,先大致了解dubbo-SPI的应用
2、dubbo框架的入口,这里是介绍spring的可拓展schema
3、API和SPI的区别
4、Java-SPI机制的介绍
5、dubbo-SPI机制的介绍

下面进入正文

1、dubbo框架分层


  • config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
  • proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
  • registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
  • cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
  • monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
  • protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
  • exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
  • transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
  • serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
上面是dubbo官方文档向我们展示的dubbo框架的核心分层,每层的核心类及其分层,类之间的调用和依赖关系。
这个图看起来还是很复杂的,从这个图我们需要掌握一下的信息:
(1)每层的作用
(2)每层的核心类和核心方法
(3)框架使用了API和SPI两个扩展机制


我把下面八层的部分实现写出来了,这只是dubbo现在支持的实现的一小部分而已,具体的可选择的实现可以到dubbo官网查询。
例如Protocol层,上面列举了DubboProtocol和RedisProtocol两个实现的Protocol,除此之外,还有下面的各种实现方式

但是在实际使用的时候,我们只需要使用一种实现,那dubbo是怎么选择的呢。

在dubbo中,是通过下面这行代码获取的

private Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
复制代码

那里面到底是怎么实现的呢,其实就是使用了SPI机制


2、spring的可拓展schema
如果采用xml配置(官方推荐)来配置dubbo项目,其实就是使用了Spring可扩展Schema机制
要扩展Schema有一下几个步骤
(1)设计配置属性和JavaBean
(2)编写XSD文件
(3)编写NamespaceHandler和BeanDefinitionParser完成解析工作
(4)编写spring.handlers和spring.schemas串起所有部件
(5)在Bean文件中应用


我们通过Schema机制的原理,可以找到dubbo项目启动的入口,方便进行调试和定位问题
我们主要看两个类,NamespaceHandler和BeanDefinitionParser

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
    }

}复制代码

public class DubboBeanDefinitionParser implements BeanDefinitionParser {

    private static final Logger logger = LoggerFactory.getLogger(DubboBeanDefinitionParser.class);
    private static final Pattern GROUP_AND_VERION = Pattern.compile("^[\\-.0-9_a-zA-Z]+(\\:[\\-.0-9_a-zA-Z]+)?$");
    private final Class beanClass;
    private final boolean required;

    public DubboBeanDefinitionParser(Class beanClass, boolean required) {
        this.beanClass = beanClass;
        this.required = required;
    }

    @SuppressWarnings("unchecked")
    private static BeanDefinition parse(Element element, ParserContext parserContext, Class beanClass, boolean required) {
    }

    private static boolean isPrimitive(Class cls) {
    }

    @SuppressWarnings("unchecked")
    private static void parseMultiRef(String property, String value, RootBeanDefinition beanDefinition,
                                      ParserContext parserContext) {
       
    }

    private static void parseNested(Element element, ParserContext parserContext, Class beanClass, boolean required, String tag, String property, String ref, BeanDefinition beanDefinition) {
      
    }

    private static void parseProperties(NodeList nodeList, RootBeanDefinition beanDefinition) {
      
    }

    @SuppressWarnings("unchecked")
    private static ManagedMap parseParameters(NodeList nodeList, RootBeanDefinition beanDefinition) {
       
    }

    @SuppressWarnings("unchecked")
    private static void parseMethods(String id, NodeList nodeList, RootBeanDefinition beanDefinition,
                                     ParserContext parserContext) {
        
    }

    @SuppressWarnings("unchecked")
    private static void parseArguments(String id, NodeList nodeList, RootBeanDefinition beanDefinition,
                                       ParserContext parserContext) {
       
    }

    public BeanDefinition parse(Element element, ParserContext parserContext) {
      
    }

复制代码

上面的dubbo扩展schema的启动类入口,这里完成的逻辑就是把xml的配置信息,装入dubbo的配置JavaBean,并做一下特定的操作。


例如我们看 ServiceBean这个类,这个类是service标签的JavaBean

public class ServiceBean extends ServiceConfig implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware {复制代码

我们可以看到,这个类实现了InitializingBean接口,所以当类加载完后,会进行下面的逻辑

public void afterPropertiesSet() throws Exception {
    if (getProvider() == null) {

    }
    if (getApplication() == null{
       
    }
    if (getModule() == null
            && (getProvider() == null || getProvider().getModule() == null)) {
        
    }
    if ((getRegistries() == null || getRegistries().size() == 0)
            && (getProvider() == null || getProvider().getRegistries() == null || getProvider().getRegistries().size() == 0)
            && (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().size() == 0)) {
        Map registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
        if (registryConfigMap != null && registryConfigMap.size() > 0) 
    }
    if (getMonitor() == null
            && (getProvider() == null || getProvider().getMonitor() == null)
            && (getApplication() == null || getApplication().getMonitor() == null)) {
        Map monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
        if (monitorConfigMap != null && monitorConfigMap.size() > 0) 
    }
    if ((getProtocols() == null || getProtocols().size() == 0)
            && (getProvider() == null || getProvider().getProtocols() == null || getProvider().getProtocols().size() == 0)) {
        Map protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
        if (protocolConfigMap != null && protocolConfigMap.size() > 0) 
    }
    if (getPath() == null || getPath().length() == 0) {
        if (beanName != null && beanName.length() > 0
                && getInterface() != null && getInterface().length() > 0
                && beanName.startsWith(getInterface())) {
            setPath(beanName);
        }
    }
    if (!isDelay()) {
        export();
    }
}复制代码

其实,这里就是进行服务暴露的逻辑。

3、API和SPI的区别

相信大家对API都是比较了解的,这是软件开发的一个基本概念。下面是API和SPI的区别:

API:实现方来制定接口并完成对接口的不同实现,调用方仅仅依赖却无权选择不同实现。

SPI:调用方来制定接口,由实现方来实现,调用方来选择自己的实现。

这里有一个图可以帮助理解:


API就是,定义一个接口,由服务方实现,然后客户进行调用,如果客户需要修改服务实现,需要让服务方进行修改。

而SPI是,定义一个接口,客户去实现这个接口,然后客户去调用服务方的接口时,可以让服务方调用客户的这种实现。

这里可能比较难懂,API和SPI的面向用户和目的是不同的,API直接被应用开发人员使用,SPI被框架扩展人员使用。


4、Java-SPI机制的介绍

JDBC大家应该都了解,就是实现通过Java连接到不同数据库的。

这里我们需要了解服务方和客户分别是什么

调用方是JDK,JDK定义了一个接口,java.sql.Driver,但是没有实现这个接口。

package java.sql;

import java.util.logging.Logger;

public interface Driver {

    Connection connect(String url, java.util.Properties info)
        throws SQLException;

    boolean acceptsURL(String url) throws SQLException;

    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
                         throws SQLException;

    int getMajorVersion();

    int getMinorVersion();
    
    boolean jdbcCompliant();

    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
复制代码

实现方是数据库提供商,每一个提供商,例如mysql,oracle等等不同的数据库,对上面的接口进行不同的连接实现。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.mysql.fabric.jdbc;

import com.mysql.jdbc.ExceptionInterceptor;
import com.mysql.jdbc.NonRegisteringDriver;
import com.mysql.jdbc.Util;
import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Logger;

public class FabricMySQLDriver extends NonRegisteringDriver implements Driver {
    public static final String FABRIC_URL_PREFIX = "jdbc:mysql:fabric://";
    public static final String FABRIC_SHARD_KEY_PROPERTY_KEY = "fabricShardKey";
    public static final String FABRIC_SHARD_TABLE_PROPERTY_KEY = "fabricShardTable";
    public static final String FABRIC_SERVER_GROUP_PROPERTY_KEY = "fabricServerGroup";
    public static final String FABRIC_PROTOCOL_PROPERTY_KEY = "fabricProtocol";
    public static final String FABRIC_USERNAME_PROPERTY_KEY = "fabricUsername";
    public static final String FABRIC_PASSWORD_PROPERTY_KEY = "fabricPassword";
    public static final String FABRIC_REPORT_ERRORS_PROPERTY_KEY = "fabricReportErrors";

    public FabricMySQLDriver() throws SQLException {
    }

    public Connection connect(String url, Properties info) throws SQLException {
        Properties parsedProps = this.parseFabricURL(url, info);
        if (parsedProps == null) {
            return null;
        } else {
            parsedProps.setProperty("fabricProtocol", "http");
            if (Util.isJdbc4()) {
                try {
                    Constructor jdbc4proxy = Class.forName("com.mysql.fabric.jdbc.JDBC4FabricMySQLConnectionProxy").getConstructor(Properties.class);
                    return (Connection)Util.handleNewInstance(jdbc4proxy, new Object[]{parsedProps}, (ExceptionInterceptor)null);
                } catch (Exception var5) {
                    throw (SQLException)(new SQLException(var5.getMessage())).initCause(var5);
                }
            } else {
                return new FabricMySQLConnectionProxy(parsedProps);
            }
        }
    }

    public boolean acceptsURL(String url) throws SQLException {
        return this.parseFabricURL(url, (Properties)null) != null;
    }

    Properties parseFabricURL(String url, Properties defaults) throws SQLException {
        return !url.startsWith("jdbc:mysql:fabric://") ? null : super.parseURL(url.replaceAll("fabric:", ""), defaults);
    }

    public Logger getParentLogger() throws SQLException {
        throw new SQLException("no logging");
    }

    static {
        try {
            DriverManager.registerDriver(new FabricMySQLDriver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver", var1);
        }
    }
}
复制代码


那么我们是怎么是调用到不同厂商的实现方式的呢,这里就是用了Java-SPI机制。

实现Java-SPI有以下的步骤:

(1)在工程的META-INF/services/目录下,以接口的全限定名作为文件名,文件内容为实现接口的服务类;

(2)使用ServiceLoader动态加载META-INF/services下的实现类;

(3)接口的实现类需含无参构造函数;(因为类默认包含无参构造函数,如果我们没有重载构造函数所以此处可忽略)


到这里,大家需要了解API和SPI的区别,已经SPI的应用场景。

 (1)common-logging
apache最早提供的日志的门面接口。只有接口,没有实现。具体方案由各提供商实现, 发现日志提供商是通过扫描 META-INF/services/org.apache.commons.logging.LogFactory配置文件,通过读取该文件的内容找到日志提工商实现类。只要我们的日志实现里包含了这个文件,并在文件里制定 LogFactory工厂接口的实现类即可。

(2)jdbc
jdbc4.0以前, 开发人员还需要基于Class.forName("xxx")的方式来装载驱动,jdbc4也基于spi的机制来发现驱动提供商了,可以通过META-INF/services/java.sql.Driver文件里指定实现类的方式来暴露驱动提供者.


5、dubbo-SPI机制的介绍

Dubbo 改进了 JDK 标准的 SPI 的以下问题:

(1)JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

(2)如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

(3)增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

我们以一个例子来了解,dubbo-SPI机制的实现和原理。

private Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();复制代码

我们先看一下Protocol这个类

@SPI("dubbo")
public interface Protocol {

    int getDefaultPort();

    @Adaptive
     Exporter export(Invoker invoker) throws RpcException;

    @Adaptive
     Invoker refer(Class type, URL url) throws RpcException;

    void destroy();

}复制代码


首先,我们需要了解三个主要的注解。


(1)@SPI注解,使用在接口上,标志这个接口是一个可以进行SPI扩展的接口;注解的value值指定这个扩展的默认实现方式。

例如上面的Protocol,注解为@SPI("dubbo"),标志这个接口支持SPI扩展,也就是说可以指定下面实现类的某个实现来完成这个接口的功能


注解的value值为dubbo,表明如果没有指定protocol的实现方式,默认使用DubboProtocol作为这个接口的实现类。


(2)@Adaptive注解,用在方法上和实现类上;用在方法上时,标志这个方法是支持动态扩展的,可以在调用的时候指定特定的扩展类来实现这个方法;用在类上时,指定这个扩展的默认实现方式,这个默认实现的优先级比@SPI注解要高。

注解用在方法上:

我们可以看一下上面的Protocol的接口定义,这个接口有两个方法使用了@Adaptive注解,也就是说这两个方法在调用的时候,可以动态选择不同的扩展实现来调用,这到底是怎么实现的呢。


上面的代码,就是dubbo对protocol进行扩展后生成的类。我们可以看到,没有使用@Adaptive注解的方法,实现类上只是抛出了一个异常,并没有做特定的实现。而使用@Adaptive注解的两个方法,都是通过URL对象来进行扩展实现的选择的。


(3)@Activate注解,用在实现类上,用于标志这个类的配置信息。


@Activate注解主要使用在集合扩展类上,就是那些需要一次性扩展比较多实现的类。

例如上面的三个类,@Activate注解的group = Constants.PROVIDER,表明这些类的扩展实现都是在URL带用Constants.PROVIDER的时候生效的,他们会生成一个链式的过滤器,对请求进行特定的行为处理。



你可能感兴趣的:(java,数据库,ruby)