05.Dubbo 源碼解析之服務暴露

1. 環境搭建

  • 代碼已經上傳至 https://github.com/masteryourself/dubbo ,分支名稱是 masteryourself-2.7.3-release

  • provider 是 dubbo-demo-xml-provider 工程,啓動類是 Application

  • consumer 是 dubbo-demo-xml-consumer 工程,啓動類是 Application

2. 源碼解析

2.1 關於 ServiceBean

image

  • 由於 ServiceBean 實現了 ApplicationListener<ContextRefreshedEvent> 接口,所以會在 onApplicationEvent 方法中監聽 ContextRefreshedEvent 事件,當容器刷新完畢後,會調用 export() 方法完成服務暴露

2.2 流程預覽

image

// 1. 監聽 spring 的 【ContextRefreshedEvent】 事件
org.apache.dubbo.config.spring.ServiceBean#onApplicationEvent ->
	// 調用 export() 方法完成服務暴露
	org.apache.dubbo.config.spring.ServiceBean#export ->
		// 調用父類 【ServiceConfig】 完成服務暴露
		org.apache.dubbo.config.ServiceConfig#export ->
			
			// 1.1 檢查和更新屬性
			org.apache.dubbo.config.ServiceConfig#checkAndUpdateSubConfigs  ->
				// 設置全局的默認屬性,因爲 serviceBean 和 application、registries 等配置類都有關聯關係
				org.apache.dubbo.config.ServiceConfig#completeCompoundConfigs
				// 啓動全局配置中心
				org.apache.dubbo.config.AbstractInterfaceConfig#startConfigCenter ->
					// 刷新 ApplicationConfig、MonitorConfig、ModuleConfig、ProtocolConfig、RegistryConfig、ProviderConfig、ConsumerConfig 配置
					org.apache.dubbo.config.context.ConfigManager#refreshAll

						// 1.1.1(*) 獲取混合配置,給 set 開頭的方法賦值,即完成屬性賦值
						// 配置優先級默認是:系統環境變量 -> 配置中心某個應用的配置 -> 配置中心的全局配置 -> AbstractConfig 類的屬性值 -> 
							// dubbo.properties.file 或 dubbo.properties 文件配置
						org.apache.dubbo.config.AbstractConfig#refresh

							// 1.1.1.1(*) 初始化 compositeConfiguration 混合配置 list
							org.apache.dubbo.common.config.Environment#getConfiguration

			// 1.2 導出服務
			org.apache.dubbo.config.ServiceConfig#doExport
				
				// 1.2.1(*) 循環所有的 protocols,依次進行服務暴露,這裏的 protocols 有 2 個,因此一共會暴露 4 個地址
				// 0 = {ProtocolConfig@2823} "<dubbo:protocol name="dubbo" port="20880" valid="true" id="dubbo" prefix="dubbo.protocols." />"
	        	// 1 = {ProtocolConfig@2840} "<dubbo:protocol name="dubbo" port="20881" valid="true" id="dubbo2" prefix="dubbo.protocols." />"
				org.apache.dubbo.config.ServiceConfig#doExportUrls

					// 1.2.1.1 獲取要導出的服務 url,格式如下:
				    // 0 = {URL@2835} "registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&
						// dubbo=2.0.2&pid=7208&qos-port=22222&registry=zookeeper&timestamp=1577007384243"
	        		// 1 = {URL@2836} "registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&
						// dubbo=2.0.2&pid=7208&qos-port=22222&registry=zookeeper&timestamp=1577007384245&version=1.1.0"
					org.apache.dubbo.config.AbstractInterfaceConfig#loadRegistries

					// 1.2.1.2(*) 調用此方法進行服務暴露
					org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol

						// 1.2.1.2.1(*) 改寫 url,把協議改成 injvm 協議,host 設置爲 127.0.0.1,端口號設置爲 0
						// 如果配置了 remote,則表示禁止使用 injvm 協議,就不會進行本地暴露
						org.apache.dubbo.config.ServiceConfig#exportLocal ->
							// 調用 Protocol 的動態代理類 【Protocol$Adaptive】 的 export 方法,先經過 wrapper 包裝類,再到 【InjvmProtocol】,因爲協議被改成了 injvm
							org.apache.dubbo.rpc.Protocol$Adaptive#export ->
								// ProtocolListener 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 injvm,所以會起作用
								// 服務導出之後,可以用監聽器做一些操作
								org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper#export ->
									// ProtocolFilter 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 injvm,所以會起作用
									org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#export

										// 1.2.1.2.1.1(*) 構建 filterChain,實現類最終會被包裝成 
										// EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter -> TraceFilter -> TimeoutFilter -> 
											// MonitorFilter -> ExceptionFilter -> DemoServiceImpl
										org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#buildInvokerChain

									// InjvmProtocol,真正處理 injvm 協議的 protocol 類
									org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol#export ->
										// 在構造方法裏把 invoker 添加到了 【exporterMap】 屬性中,在 injvm 協議調用時會從此 map 取值
										// "org.apache.dubbo.demo.DemoService" -> "org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$CallbackRegistrationInvoker@5827af16"
										org.apache.dubbo.rpc.protocol.injvm.InjvmExporter#<init>

						// 1.2.1.2.2 循環所有的 registryURLs,作遠程服務導出,形如:
						// registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&
							// export=dubbo%3A%2F%2F192.168.89.1%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3D
							// demo-provider%26bean.name%3Dorg.apache.dubbo.demo.DemoService%26bind.ip%3D192.168.89.1%26bind.port%3D20880%26
							// deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26
							// methods%3DsayHello%26pid%3D18816%26qos-port%3D22222%26register%3Dtrue%26release%3D%26side%3Dprovider%26
							// timestamp%3D1577009068166&pid=18816&qos-port=22222&registry=zookeeper&timestamp=1577009067710
						// 調用 Protocol 的動態代理類 【Protocol$Adaptive】 的 export 方法,先經過 wrapper 包裝類,再到 【RegistryProtocol】,因爲協議是 registry
						org.apache.dubbo.rpc.Protocol$Adaptive#export ->
							// ProtocolListener 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 registry,所以不會起作用
							// 服務導出之後,可以用監聽器做一些操作
							org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper#export ->
								// ProtocolFilter 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 registry,所以不會起作用
								org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#export

									// 1.2.1.2.2.1(*) 添加監聽器,暴露遠程服務,連接 zk 創建節點
									org.apache.dubbo.registry.integration.RegistryProtocol#export

										// 1.2.1.2.2.1.1 暴露遠程服務,形如:
										// dubbo://192.168.89.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&
											// bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.89.1&bind.port=20880&deprecated=false&
											// dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&
											// pid=8724&qos-port=22222&register=true&release=&side=provider&timestamp=1577011539455
										org.apache.dubbo.registry.integration.RegistryProtocol#doLocalExport ->
											// 調用 Protocol 的動態代理類 【Protocol$Adaptive】 的 export 方法,先經過 wrapper 包裝類,再到 【DubboProtocol】
											org.apache.dubbo.rpc.Protocol$Adaptive#export ->
												// ProtocolListener 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 dubbo,所以會起作用
												// 服務導出之後,可以用監聽器做一些操作
												org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper#export ->
													// ProtocolFilter 包裝類,當 protocol 不爲 registry 時纔會起作用,這裏是 dubbo,所以會起作用同 【1.2.1.2.1.1】
													org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#export ->
														// 獲取 url,暴露 netty 遠程服務
														org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export ->
															// 獲取 netty server,綁定服務名稱和端口號
															org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#openServer

										// 1.2.1.2.2.1.2 把服務註冊到 zk 上
										org.apache.dubbo.registry.integration.RegistryProtocol#register ->
											// 調用 zookeeper 的 doRegister 方法,把服務路徑寫到 zk 節點上
											org.apache.dubbo.registry.support.FailbackRegistry#doRegister

2.3 流程詳解

2.3.1 AbstractConfig#refresh(1.1.1)
  • org.apache.dubbo.config.AbstractConfig
public void refresh() {
    try {

        // 獲取混合配置
        // 1. 系統環境變量
        // 2. 配置中心某個應用的配置
        // 3. 配置中心的全局配置
        // 4. dubbo.properties.file 或 dubbo.properties 文件配置
        CompositeConfiguration compositeConfiguration = Environment.getInstance().getConfiguration(getPrefix(), getId());

        // 獲取 AbstractConfig 類中屬性的值,即調用 getXxx 或 isXxx 方法返回的所有 metaData 值
        InmemoryConfiguration config = new InmemoryConfiguration(getPrefix(), getId());
        config.addProperties(getMetaData());

        // 判斷是否是配置中心的配置優先
        // 配置優先級默認是:系統環境變量 -> 配置中心某個應用的配置 -> 配置中心的全局配置 -> AbstractConfig 類的屬性值 ->
        // dubbo.properties.file 或 dubbo.properties 文件配置
        if (Environment.getInstance().isConfigCenterFirst()) {
            // The sequence would be: SystemConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AbstractConfig -> PropertiesConfiguration
            compositeConfiguration.addConfiguration(3, config);
        } else {
            // The sequence would be: SystemConfiguration -> AbstractConfig -> AppExternalConfiguration -> ExternalConfiguration -> PropertiesConfiguration
            compositeConfiguration.addConfiguration(1, config);
        }

        // loop methods, get override value and set the new value back to method
        Method[] methods = getClass().getMethods();
        for (Method method : methods) {
            // 獲取 set 開頭的方法
            if (MethodUtils.isSetter(method)) {
                try {

                    // 根據 setXxx 的 xxx 屬性,從 compositeConfiguration 混合配置中獲取屬性對應的值
                    String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method)));
                    // isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig.
                    if (StringUtils.isNotEmpty(value) && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)) {

                        // 反射調用 set 方法賦值
                        method.invoke(this, ClassUtils.convertPrimitive(method.getParameterTypes()[0], value));
                    }
                } catch (NoSuchMethodException e) {
                    logger.info("Failed to override the property " + method.getName() + " in " +
                            this.getClass().getSimpleName() +
                            ", please make sure every property has getter/setter method provided.");
                }
            }
        }
    } catch (Exception e) {
        logger.error("Failed to override ", e);
    }
}
2.3.2 Environment#getConfiguration(1.1.1.1)
  • org.apache.dubbo.common.config.Environment
public CompositeConfiguration getConfiguration(String prefix, String id) {
    CompositeConfiguration compositeConfiguration = new CompositeConfiguration();
    // Config center has the highest priority

    // 從系統環境變量中獲取值
    compositeConfiguration.addConfiguration(this.getSystemConfig(prefix, id));

    // 從配置中心的 dubbo.config.xxx.dubbo.properties 節點獲取值(xxx 應用的配置)
    compositeConfiguration.addConfiguration(this.getAppExternalConfig(prefix, id));

    // 從配置中心的 dubbo.config.dubbo.dubbo.properties 節點獲取值(全局配置)
    compositeConfiguration.addConfiguration(this.getExternalConfig(prefix, id));

    // 從 dubbo.properties.file 或者 dubbo.properties 文件中獲取值
    compositeConfiguration.addConfiguration(this.getPropertiesConfig(prefix, id));
    return compositeConfiguration;
}
2.3.3 ServiceConfig#doExportUrls(1.2.1)
  • org.apache.dubbo.config.ServiceConfig
private void doExportUrls() {

    // 獲取要導出的服務 url,格式如下
    // 0 = {URL@2835} "registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&
    // dubbo=2.0.2&pid=7208&qos-port=22222&registry=zookeeper&timestamp=1577007384243"
    // 1 = {URL@2836} "registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&
    // dubbo=2.0.2&pid=7208&qos-port=22222&registry=zookeeper&timestamp=1577007384245&version=1.1.0"
    List<URL> registryURLs = loadRegistries(true);

    // 循環所有的 protocols,挨個導出服務,protocols 有如下兩個,所以一共會暴露 4 個服務
    // 0 = {ProtocolConfig@2823} "<dubbo:protocol name="dubbo" port="20880" valid="true" id="dubbo" prefix="dubbo.protocols." />"
    // 1 = {ProtocolConfig@2840} "<dubbo:protocol name="dubbo" port="20881" valid="true" id="dubbo2" prefix="dubbo.protocols." />"
    for (ProtocolConfig protocolConfig : protocols) {
        String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
        ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
        ApplicationModel.initProviderModel(pathKey, providerModel);

        // 調用此方法進行服務暴露
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}
2.3.4 ServiceConfig#doExportUrlsFor1Protocol(1.2.1.2)
  • org.apache.dubbo.config.ServiceConfig
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    // 獲取協議名稱,如果是空,默認爲 dubbo 協議
        String name = protocolConfig.getName();
    if (StringUtils.isEmpty(name)) {
        name = DUBBO;
    }

    Map<String, String> map = new HashMap<String, String>();
    map.put(SIDE_KEY, PROVIDER_SIDE);

    // 屬性覆蓋
    appendRuntimeParameters(map);
    appendParameters(map, metrics);
    appendParameters(map, application);
    appendParameters(map, module);
    // remove 'default.' prefix for configs from ProviderConfig
    // appendParameters(map, provider, Constants.DEFAULT_KEY);
    appendParameters(map, provider);
    appendParameters(map, protocolConfig);
    appendParameters(map, this);
    
    ...

    // export service
    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = this.findConfigedPorts(protocolConfig, name, map);

    // 構造一個要導出的 url
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

    ...
    
    if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

        // export to local if the config is not remote (export to remote only when config is remote)
        // 如果配置了 remote,則表示禁止使用 injvm 協議,就不會進行本地暴露
        if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
            exportLocal(url);
        }
        // export to remote if the config is not local (export to local only when config is local)
        if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
            if (!isOnlyInJvm() && logger.isInfoEnabled()) {
                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
            }
            if (CollectionUtils.isNotEmpty(registryURLs)) {

                // 循環所有的 registryURLs
                for (URL registryURL : registryURLs) {
                
                    ...

                    // 暴露遠程服務
                    // registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.89.1%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bean.name%3Dorg.apache.dubbo.demo.DemoService%26bind.ip%3D192.168.89.1%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D21564%26qos-port%3D22222%26register%3Dtrue%26release%3D%26side%3Dprovider%26timestamp%3D1577013554161&pid=21564&qos-port=22222&registry=zookeeper&timestamp=1577013552772
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            } else {
                
                ...
                
            }
            
            ...
            
        }
    }
    this.urls.add(url);
}
2.3.5 ServiceConfig#exportLocal(1.2.1.2.1)
  • org.apache.dubbo.config.ServiceConfig
private void exportLocal(URL url) {
    // 改寫 url,把協議改成 injvm 協議,host 設置爲 127.0.0.1,端口號設置爲 0
    URL local = URLBuilder.from(url)
            .setProtocol(LOCAL_PROTOCOL)
            .setHost(LOCALHOST_VALUE)
            .setPort(0)
            .build();

    // 本地服務導出
    Exporter<?> exporter = protocol.export(
            PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
    exporters.add(exporter);
    logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}
2.3.6 ProtocolFilterWrapper#buildInvokerChain(1.2.1.2.1.1)
  • org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    // 獲取所有激活的 filter Extension
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

    if (!filters.isEmpty()) {

        // 循環包裝
        for (int i = filters.size() - 1; i >= 0; i--) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {

                @Override
                public Class<T> getInterface() {
                    return invoker.getInterface();
                }

                @Override
                public URL getUrl() {
                    return invoker.getUrl();
                }

                @Override
                public boolean isAvailable() {
                    return invoker.isAvailable();
                }

                @Override
                public Result invoke(Invocation invocation) throws RpcException {
                    Result asyncResult;
                    try {
                        asyncResult = filter.invoke(next, invocation);
                    } catch (Exception e) {
                        // onError callback
                        if (filter instanceof ListenableFilter) {
                            Filter.Listener listener = ((ListenableFilter) filter).listener();
                            if (listener != null) {
                                listener.onError(e, invoker, invocation);
                            }
                        }
                        throw e;
                    }
                    return asyncResult;
                }

                @Override
                public void destroy() {
                    invoker.destroy();
                }

                @Override
                public String toString() {
                    return invoker.toString();
                }
            };
        }
    }

    return new CallbackRegistrationInvoker<>(last, filters);
}
2.3.7 RegistryProtocol#export(1.2.1.2.2.1)
  • org.apache.dubbo.registry.integration.RegistryProtocol#export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    // 獲取註冊中心地址
    // zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&export=dubbo%3A%2F%2F192.168.89.1%3A20880%2Forg.apache.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bean.name%3Dorg.apache.dubbo.demo.DemoService%26bind.ip%3D192.168.89.1%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D18816%26qos-port%3D22222%26register%3Dtrue%26release%3D%26side%3Dprovider%26timestamp%3D1577009068166&pid=18816&qos-port=22222&timestamp=1577009067710
    URL registryUrl = getRegistryUrl(originInvoker);

    // url to export locally
    // 獲取導出地址
    // dubbo://192.168.89.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.89.1&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=18816&qos-port=22222&register=true&release=&side=provider&timestamp=1577009068166
    URL providerUrl = getProviderUrl(originInvoker);

    // Subscribe the override data
    // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
    //  the same service. Because the subscribed is cached key with the name of the service, it causes the
    //  subscription information to cover.

    // 獲取能覆蓋配置的訂閱地址
    // provider://192.168.89.1:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=org.apache.dubbo.demo.DemoService&bind.ip=192.168.89.1&bind.port=20880&category=configurators&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=1108&qos-port=22222&register=true&release=&side=provider&timestamp=1577010446361
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);

    // 添加監聽器
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    //export invoker
    // 調用 originInvoker 的 protocol 實現,進行對應協議的服務暴露,這裏是【DubboProtocol】
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

    // url to registry
    // 獲取 registry,這裏是 【ZookeeperRegistry】
    final Registry registry = getRegistry(originInvoker);

    // 簡化 url,因爲新版本多了配置中心
    final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
    ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
            registryUrl, registeredProviderUrl);

    //to judge if we need to delay publish
    // 判斷是否需要註冊
    boolean register = registeredProviderUrl.getParameter("register", true);
    if (register) {

        // 服務註冊
        register(registryUrl, registeredProviderUrl);
        providerInvokerWrapper.setReg(true);
    }

    // Deprecated! Subscribe to override rules in 2.6.x or before.
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    exporter.setRegisterUrl(registeredProviderUrl);
    exporter.setSubscribeUrl(overrideSubscribeUrl);
    //Ensure that a new exporter instance is returned every time export
    return new DestroyableExporter<>(exporter);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章