生成URL

dubbo的功能,就是一个RPC框架,那么自然最重要的就是服务的发布,和服务的调用。

现在我们只有一个xml,然后spring帮我们读取生成ServiceBean,所以现在我们只有一个接口名字,需要暴露的实现类

...
    <!-- 提供方应用信息,用于计算依赖关系 -->
    <dubbo:application name="hello-world-app"  />
    <!-- 使用multicast广播注册中心暴露服务地址 -->
    <dubbo:registry address="multicast://224.5.6.7:1234" />

    <!-- 用dubbo协议在20880端口暴露服务 -->
    <dubbo:protocol name="dubbo" port="20880" />
    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" />
    <!-- 和本地bean一样实现服务 -->
    <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl" />
</beans>

  dubbo的启动流程中,首先是启动spring的容器,加载bean,然后加载DubboBootStrap类,实例化,连接注册中心,最后才开始调用服务暴露,也就是ServiceBean父子系统的export()方法。

dubbo服务暴露的入口在DubboBootStrap类start()方法中,在调用export()方法之前,回进行注册中心的连接,所以dubbo的启动,核心是在DubboBootStrap类中。

  调用ServiceConfig的export()方法,【注意】,调用一次export()就是暴露一个接口服务,如果有多个服务需要暴露,则在DubboBootStrap回多次调用export()方法,所以我们直接从ServiceConfig开始,看一下继承图,七层妖塔

dubbo暴露接口 dubbo怎么暴露接口的_List

看一下ServiceConfig的成员,内容不多,其实大部分字段放 在了父类ServiceConfigBase里面

private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
   //默认实现 JavassistProxyFactory
    private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

    private transient volatile boolean exported; //服务是否暴露
    private transient volatile boolean unexported;//默认为false

    private DubboBootstrap bootstrap;
    private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>(); //The exported services

 父类ServiceConfigBase字段:

protected String interfaceName;    //暴露服务接口 名
    protected Class<?> interfaceClass; //暴露服务接口 Class对象
    protected T ref;        //接口 实现
    protected String path;  //服务 名字
    protected ProviderConfig provider;  //服务提供者的配置
    protected String providerIds;
    protected volatile String generic;  //通用service
    protected ServiceMetadata serviceMetadata;

服务暴露入口:此处,已经获得了service的所有信息,在下面的set方法中注入,包括Class信息,名字等等。

public synchronized void export() {
        if (!shouldExport()) { return; }
        //启动引导类
        if (bootstrap == null) {
            bootstrap = DubboBootstrap.getInstance();
            bootstrap.init();
        }

        checkAndUpdateSubConfigs();  // ★ 1

        //init serviceMetadata
        serviceMetadata.setVersion(getVersion());
        serviceMetadata.setGroup(getGroup());
        serviceMetadata.setDefaultGroup(getGroup());
        serviceMetadata.setServiceType(getInterfaceClass());
        serviceMetadata.setServiceInterfaceName(getInterface());
        serviceMetadata.setTarget(getRef());

        if (shouldDelay()) { //是否需要延迟 暴露
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
            doExport(); //★ 2【入】
        }
        exported();
    }

1.checkAndUpdateSubConfigs()

各种配置信息检测(代码很长。省略异常部分)

private void checkAndUpdateSubConfigs() {
        //下面三个方法都在 父类ServiceConfigBase中
        completeCompoundConfigs();  
        checkDefault();
        checkProtocol();
        // init some null configuration. SPI机制
        List<ConfigInitializer> configInitializers = ExtensionLoader.getExtensionLoader(ConfigInitializer.class)
                .getActivateExtension(URL.valueOf("configInitializer://"), (String[]) null);
        configInitializers.forEach(e -> e.initServiceConfig(this));

        // 如果协议不止injvm 那么就要检测
        if (!isOnlyInJvm()) {
            checkRegistry();
        }
        this.refresh(); //顶层父类AbstractConfig 中的方法。

        if (StringUtils.isEmpty(interfaceName)) {  //服务接口名字不能为空
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }
        //-----检测 ref 是否为泛化服务类型
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class; // 设置 interfaceClass 为 GenericService.class
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString(); // 设置 generic = "true"
            }
        } else {
                //反射获取接口 Class对象
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
          
            // 对 interfaceClass,以及 <dubbo:method> 标签中的必要字段进行检查
            checkInterfaceAndMethods(interfaceClass, getMethods()); //这个方法在父类中
            checkRef(); //检测接口是否实现
            generic = Boolean.FALSE.toString(); // 设置 generic = "false"
        }
        // local 和 stub 在功能应该是一致的,用于配置本地存根
        if (local != null) {
            if ("true".equals(local)) {
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            // 获取本地存根类
           localClass = ClassUtils.forNameWithThreadContextClassLoader(local);
           // 检测本地存根类是否可赋值给接口类,若不可赋值则会抛出异常,提醒使用者本地存根类类型不合法
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        if (stub != null) {
            if ("true".equals(stub)) {
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            stubClass = ClassUtils.forNameWithThreadContextClassLoader(stub);

        }
        // 检测各种对象是否为空,为空则新建,或者抛出异常
        checkStubAndLocal(interfaceClass);
        ConfigValidationUtils.checkMock(interfaceClass, this);
        ConfigValidationUtils.validateServiceConfig(this);
        postProcessConfig();
    }

2.doExport()

服务暴露中,它的流程基本就是一条流水线,它的回调很少,基本都是往前走)内容比较简单,只是一个入口。

protected synchronized void doExport() {
        if (exported) {
            return;
        }
        exported = true; //暴露打开了!
        if (StringUtils.isEmpty(path)) {
            path = interfaceName;
        }
        //暴露服务 【入】
        doExportUrls(); //★ 3
    }

3. doExportUrls()

思路:

1.获得普通的所有service,

2.然后先将本次需要暴露的service加入到仓库中,然后再根据服务是消费者还是提供者,再一次放入分类的仓库中

3.根据协议数量,一次按协议进行暴露

private void doExportUrls() {
        //3.1 service仓库
        ServiceRepository repository = ApplicationModel.getServiceRepository();
        //3.2 service描述
        ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
        //3.3 注册providerModel
        repository.registerProvider(
                getUniqueServiceName(),
                ref,
                serviceDescriptor,
                this,
                serviceMetadata
        );
        //3.4 加载 注册中心链接
        List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
        //遍历协议,在每个协议下,暴露服务
        for (ProtocolConfig protocolConfig : protocols) {
            String pathKey = URL.buildKey(getContextPath(protocolConfig)
                    .map(p -> p + "/" + path)
                    .orElse(path), group, version);
            // In case user specified path, register service one more time to map it to path.
            repository.registerService(pathKey, interfaceClass);
            // TODO, uncomment this line once service key is unified
            serviceMetadata.setServiceKey(pathKey);
            doExportUrlsFor1Protocol(protocolConfig, registryURLs); //【入】★4号
        }
    }

3.1 ServiceRepository

从它的名字,自然可以看出来,它就是一个容器,它把service分为了四类保存,

public class ServiceRepository extends LifecycleAdapter implements FrameworkExt {
 // services
    private ConcurrentMap<String, ServiceDescriptor> services = new ConcurrentHashMap<>();
    // consumers
    private ConcurrentMap<String, ConsumerModel> consumers = new ConcurrentHashMap<>();
    // providers
    private ConcurrentMap<String, ProviderModel> providers = new ConcurrentHashMap<>();
    // useful to find a provider model quickly with serviceInterfaceName:version
    private ConcurrentMap<String, ProviderModel> providersWithoutGroup = new ConcurrentHashMap<>();

    //将 接口Class对象,封装 放入缓冲中
    public ServiceDescriptor registerService(String path, Class<?> interfaceClass) {
        ServiceDescriptor serviceDescriptor = registerService(interfaceClass); //将接口Class对象 封装
        //路径 不相同 才放入...
        if (!interfaceClass.getName().equals(path)) {
            services.putIfAbsent(path, serviceDescriptor);
        }
        return serviceDescriptor;
    }
}(省略版)

 

3.2 ServiceDescriptor 服务封装

它的组成成分:1.名字,2.Class对象,3.方法列表(包括了重载)

public class ServiceDescriptor {
    private final String serviceName;
    private final Class<?> serviceInterfaceClass;
    // 方法name-方法descriptor List(因为一个name可能重载,出现多个实现)
    private final Map<String, List<MethodDescriptor>> methods = new HashMap<>();
    //增加 参数描述
    private final Map<String, Map<String, MethodDescriptor>> descToMethods = new HashMap<>();
   public ServiceDescriptor(Class<?> interfaceClass) {
        this.serviceInterfaceClass = interfaceClass;
        this.serviceName = interfaceClass.getName();
        initMethods(); //方法描述类,初始化
    }
    private void initMethods() {
        Method[] methodsToExport = this.serviceInterfaceClass.getMethods(); //反射获得所有方法
        for (Method method : methodsToExport) { //遍历方法,封装放到容器中
            method.setAccessible(true);
            List<MethodDescriptor> methodModels = methods.computeIfAbsent(method.getName(), (k) -> new ArrayList<>(1));
            methodModels.add(new MethodDescriptor(method));
        }
        //再来一个便于查找具体method  的容器
        methods.forEach((methodName, methodList) -> {
            Map<String, MethodDescriptor> descMap = descToMethods.computeIfAbsent(methodName, k -> new HashMap<>());
            methodList.forEach(methodModel -> descMap.put(methodModel.getParamDesc(), methodModel));
        });
    }
}

我们简单看一下MethodDescriptor

public class MethodDescriptor {
    private final Method method;
    //    private final boolean isCallBack;
//    private final boolean isFuture;
    private final String paramDesc;           //参数类型 名字 合集
    // duplicate filed as paramDesc, but with different format.
    private final String[] compatibleParamSignatures;
    private final Class<?>[] parameterClasses; // 参数类型 Class对象
    private final Class<?> returnClass;       //返回类型 Class对象
    private final Type[] returnTypes;         //楼上的封装
    private final String methodName;
    private final boolean generic;
...
}

3.3 registerProvider()  注册提供者

providerModel生成,内容简单,new出来,也就是对基础信息的包装,然后放到缓冲中,serviceKey是服务的唯一名字

public class ProviderModel {
    private String serviceKey;  //serviceId
    private final Object serviceInstance; //service 实例
    private final ServiceDescriptor serviceModel; //接口描述类
    private final ServiceConfigBase<?> serviceConfig; //启动,暴露类
    private final List<RegisterStatedURL> urls; //暴露的路径,
...
}

梳理一下三者的关系:

服务注册的仓库,也就是总容器:serviceRepository,它的里面存放四个内容:serviceDescriptorConsumerModelProviderModelProvidersWithoutGroup

serviceDescriptor是service接口的包装类,它里面有method的信息,

ProviderModel是 service提供者的包装,里面有serviceId,实现类,暴露类,接口描述类等等字段。

注册完毕之后的状态:红色箭头是新增的,也就上方两个方法注册进入的:

dubbo暴露接口 dubbo怎么暴露接口的_List_02

3.4 loadRegistries() 加载注册中心链接

传入的参数:ServiceConfig实例,true,这个和我们的配置信息有关,dubbo不仅支持多协议,而且注册多注册中心,所以这里的注册中心链接是list集合,我们demo配置了一个注册中心,所以size=1。

//加载注册中心 链接                       参数:ServiceCOnfig  ,true
    public static List<URL> loadRegistries(AbstractInterfaceConfig interfaceConfig, boolean provider) {
        // check && override if necessary
        List<URL> registryList = new ArrayList<URL>();
        ApplicationConfig application = interfaceConfig.getApplication(); //获得配置 都集成自AbstactConfig类
        List<RegistryConfig> registries = interfaceConfig.getRegistries(); //注册信息
        // 解析得到URL
        if (CollectionUtils.isNotEmpty(registries)) {
            for (RegistryConfig config : registries) {
                String address = config.getAddress();
                if (StringUtils.isEmpty(address)) {
                    address = ANYHOST_VALUE; //默认地址
                }
                if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map<String, String> map = new HashMap<String, String>();
                    //添加形体 到map中
                    AbstractConfig.appendParameters(map, application);
                    AbstractConfig.appendParameters(map, config);
                    map.put(PATH_KEY, RegistryService.class.getName());

                    AbstractInterfaceConfig.appendRuntimeParameters(map);
                    if (!map.containsKey(PROTOCOL_KEY)) {
                        map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
                    }
                    //生成URL,这里产生的是一个列表,解析得到 URL 列表,address 可能包含多个注册中心 ip,
                    List<URL> urls = UrlUtils.parseURLs(address, map);

                    for (URL url : urls) {
                        //将url 协议头设置为registry,
                        url = URLBuilder.from(url)
                                .addParameter(REGISTRY_KEY, url.getProtocol())
                                .setProtocol(extractRegistryType(url))
                                .build();
                        if ((provider && url.getParameter(REGISTER_KEY, true))
                                || (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
                            registryList.add(url);  // ★ 添加
                        }
                    }
                }
            }
        }
        return registryList;
    }

dubbo暴露接口 dubbo怎么暴露接口的_ide_03

注册中心配置超级多,我们简单看一下:RegistryConfig类

public class RegistryConfig extends AbstractConfig {
    private String address;  //注册中心 地址
    private String username; //登录注册中心
    private String password;
    private Integer port;    //注册断开
    private String protocol;
    private String transporter; //网络通信方式
    private String server;
    private String client;
....
}

而ApplicationConfig,就是 相对于注册中心来说的 客户端 信息。

获取到注册中心列表:

dubbo暴露接口 dubbo怎么暴露接口的_List_04

最后进行所有协议遍历,进行服务暴露:demo中的协议信息:

dubbo暴露接口 dubbo怎么暴露接口的_List_05