preface

We covered the Dubbo initialization process in Chapter 2. Dubbo initialization occurs with the instantiation of the Spring container Bean, and today we’ll focus on one node that looks like this in the configuration file:

It completes the logic exposed by the Dubbo service, so let’s take a look at the general flow.

A, start

The processing class for the node information in the configuration file is ServiceBean. Let’s take a look at its structure

public class ServiceBean<T> extends ServiceConfig<T> implements 
			InitializingBean, DisposableBean, ApplicationContextAware,
					ApplicationListener<ContextRefreshedEvent>, BeanNameAware {}
Copy the code

As you can see, this class implements different interfaces to Spring, which means that Spring calls the corresponding methods at different times.

1. Set the context

After Spring completes instantiation and IOC, the invokeAwareInterfaces method is called to determine whether the Bean implements the Aware interface, and then the corresponding method is invoked.

public void setApplicationContext(ApplicationContext applicationContext) {
	this.applicationContext = applicationContext;
	SpringExtensionFactory.addApplicationContext(applicationContext);
	if(applicationContext ! = null) { SPRING_CONTEXT = applicationContext; try { Method method = applicationContext.getClass().getMethod("addApplicationListener", new Class<? >[]{ApplicationListener.class}); method.invoke(applicationContext, new Object[]{this}); supportedApplicationListener =true; } catch (Throwable t) {// omit irrelevant code.... }}}Copy the code

2. Initialization method

Then we see that it implements the InitializingBean interface, so the afterPropertiesSet initialization method is also not going away. In this case, you take the application information, registration information, protocol information, etc. from Dubbo and set it into variables. One final method worth noting is isDelay. When the method returns true, the export is not delayed. If false is returned, the export needs to be delayed.

public void afterPropertiesSet() throws Exception {
	if (getProvider() == null) {
		//......
	}
	if (getApplication() == null
			&& (getProvider() == null || getProvider().getApplication() == null)) {
		//......
	}
	if (getModule() == null
			&& (getProvider() == null || getProvider().getModule() == null)) {
		//......
	}
	if ((getRegistries() == null || getRegistries().isEmpty())) {
		//......
	}
	if ((getProtocols() == null || getProtocols().isEmpty())
			&& (getProvider() == null || getProvider().getProtocols() == null || 
			getProvider().getProtocols().isEmpty())) {
		//......
	}
	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();
	}
}
Copy the code

3. Exposure

This method is called after the Spring context refresh event and is the service exposed entry method.

Public void onApplicationEvent(ContextRefreshedEvent Event) {// Whether delayed exposure && Whether exposure && whether exposure has been cancelledif(isDelay() && ! isExported() && ! isUnexported()) {if (logger.isInfoEnabled()) {
			logger.info("The service ready on spring started. service: "+ getInterface()); } // Expose the serviceexport();
	}
}
Copy the code

Second, preparation

Before exposing the service, Dubbo checks various configurations, sets parameter information, adds some default configuration items, and then encapsulates URL object information. Here, we must focus on the URL object. Dubbo uses the URL as the configuration carrier, and all extension points get the configuration from the URL.

1. Check the configuration

The export() method has two configuration items to check before the service is exposed. Whether services are exposed and delayed.

public synchronized void export() {// getexportAnd delay the configurationif(provider ! = null) {if (export == null) {
			export = provider.getExport();
		}
		if(delay == null) { delay = provider.getDelay(); }} // Ifexportfalse, the service is not exposedif (export! = null && !export) {
		return; } // delay > 0, delay exposed serviceif(delay ! = null && delay > 0) { delayExportExecutor.schedule(newRunnable() {
			@Override
			public void run() {
				doExport();
			}
		}, delay, TimeUnit.MILLISECONDS);
	}else {
		doExport(); }}Copy the code

After Dubbo decided to expose the service, there were a few more things to do.

Check the validity of service interfaces. Check whether Conifg core configuration classes are empty. If they are empty, obtain corresponding instances from other configurations

protected synchronized void doExport() {
	if (unexported) {
		throw new IllegalStateException("Already unexported!");
	}
	if (exported) {
		return;
	}
	exported = true;
	if (interfaceName == null || interfaceName.length() == 0) {
		throw new IllegalStateException(" 
       interface not allow null!");
	}
	checkDefault();
	if(provider ! = null) {// Check if the configuration object is empty and assigned}if(module ! = null) {// Check if the configuration object is empty and assigned}if(application ! = null) {// Check whether the configuration object is empty and assign a value} // distinguish between generalization classes and ordinary classesif (ref instanceof GenericService) {
		interfaceClass = GenericService.class;
		if(StringUtils.isEmpty(generic)) { generic = Boolean.TRUE.toString(); }}else{// return the interfaceClass object and check the try {interfaceClass = class.forname (interfaceName,true, Thread.currentThread() .getContextClassLoader()); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } checkInterfaceAndMethods(interfaceClass, methods); checkRef(); generic = Boolean.FALSE.toString(); } // Check whether various objects are empty, create or throw the checkApplication() exception; checkRegistry(); checkProtocol(); appendProperties(this); checkStubAndMock(interfaceClass);if(path == null || path.length() == 0) { path = interfaceName; } // Service exposuredoExportUrls();
	ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
	ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
Copy the code

2. Multi-protocol and multi-registry

2.1, configuration,

Dubbo allows us to expose services using different protocols and register services with multiple registries. For example, we want to expose the service using both dubbo and RMI protocols:

<dubbo:protocol name="dubbo" port="20880"/>  
<dubbo:protocol name="rmi" port="1099" />
Copy the code

Or we need to register the service with ZooKeeper or Redis:

<dubbo:registry address=Zookeeper: / / 192.168.139.129: "2181"/>
<dubbo:registry address="Redis: / / 192.168.139.129:6379"/>
Copy the code

After this configuration, both ZooKeeper and Redis store the service information based on the two protocols.

First we execute the command: in redis hgetall/dubbo/com.viewscenes.net supervisor. Service. InfoUserService/will get the following results:

1) "Rmi://192.168.100.74:1099/com.viewscenes.net supervisor. Service. InfoUserService? Anyhost = true&application = dubbo_producer1 The supervisor & dubbo=2.6.2&generic=false&interface=com.viewscenes.net. Service. InfoUserService&methods = getUserById, getAllUser ,insertInfoUser,deleteUserById&pid=7064&side=provider&timestamp=1545804399128"
2) "1545805385379"
3) "Dubbo://192.168.100.74:20880/com.viewscenes.net supervisor. Service. InfoUserService? Anyhost = true&application = dubbo_produc Er1&dubbo=2.6.2&generic=false&interface=com.viewscenes.net supervisor. Service. InfoUserService&methods = getUserById, getAllU ser,insertInfoUser,deleteUserById&pid=7064&side=provider&timestamp=1545804391176"
4) "1545805385379"
Copy the code

At the same time, we execute the command in the zookeeper: ls/dubbo/com.viewscenes.net supervisor. Service. InfoUserService/will get the following results:

[dubbo%3A%2F%2F192.168.100.74%3A20880%2Fcom.viewscenes.net supervisor. Service. 3 fanyhost InfoUserService % % 3 dtrue% 26 application % 3 Ddubbo_producer1%26 dubbo % 3 d2. The 6.2% % 26 generic 3 dfalse%26interface%3Dcom.viewscenes.netsupervisor.service.InfoUserService%26methods%3D getUserById%2CgetAllUser%2CinsertInfoUser%2CdeleteUserById%26pid%3D7064%26side%3Dprovider%26timestamp%3D1545804391176, Rmi%3A%2F%2F192.168.100.74%3A1099%2Fcom.viewscenes.net supervisor. Service. 3 fanyhost InfoUserService % % 3 dtrue% 26 application % 3 Ddubbo_producer1%26 dubbo % 3 d2. The 6.2% % 26 generic 3 dfalse%26interface%3Dcom.viewscenes.netsupervisor.service.InfoUserService%26methods%3D
getUserById%2CgetAllUser%2CinsertInfoUser%2CdeleteUserById%26pid%3D7064%26side%3Dprovider%26timestamp%3D1545804399128]
Copy the code
2.2 Multiple registries

The loadRegistries method returns a List

registryURLs. Based on the configuration file above, registries here are a List

of length 2 that ultimately resolves to List

registryURLs.


Protected List<URL> loadRegistries(Boolean provider) {// Check the configuration checkRegistry(); List<URL> registryList = new ArrayList<URL>();if(registries ! = null && ! registries.isEmpty()) {for (RegistryConfig config : registries) {
			String address = config.getAddress();
			if(address = = null | | address. The length () = = 0) {/ / if the address is empty, it is set as 0.0.0.0 address = the ANYHOST_VALUE; } // Load registry address from System properties String sysAddress = system.getProperty ("dubbo.registry.address");
			if(sysaddress ! = null && sysaddress.length() > 0) { address = sysaddress; } // Check the validity of address and encapsulate map configuration informationif(address ! = null && address.length() > 0 && ! RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) { Map<String, String> map = new HashMap<String, String>(); appendParameters(map, application); appendParameters(map, config); map.put("path", RegistryService.class.getName());
				map.put("dubbo", Version.getVersion());
				map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
				if (ConfigUtils.getPid() > 0) {
					map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
				}
				if(! map.containsKey("protocol")) {
					if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).
                                                                hasExtension("remote")) {
						map.put("protocol"."remote");
					} else {
						map.put("protocol"."dubbo"); }} // Encapsulate the configuration information into a URL object List<URL> urls = urlutils.parseurls (address, map);for (URL url : urls) {
					url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
					url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
					if ((provider && url.getParameter(Constants.REGISTER_KEY, true) | | (! provider && url.getParameter(Constants.SUBSCRIBE_KEY,true))) {
						registryList.add(url);
					}
				}
			}
		}
	}
	return registryList;
}
Copy the code
2.3 Multi-protocol support

Multi-protocol support, too, traverses several sets of cyclic exposure services. DoExportUrlsFor1Protocol method is the specific process exposed by services.

private void doExportUrlsList<URL> registryURLs = loadRegistries()true); // Traverse the protocols, exposing a service for each protocolfor (ProtocolConfig protocolConfig : protocols) {
		doExportUrlsFor1Protocol(protocolConfig, registryURLs); }}Copy the code

3. Service exposure

After various configuration checks, come to the doExportUrlsFor1Protocol method, which exposes services based on a single protocol. The first half of the method assembles URL objects from configuration information. As we said earlier, the URL object information is crucial for Dubbo, which is the carrier of the Dubbo configuration. As far as the assembly process is concerned, it’s all about setting properties. Instead of looking at the details, let’s look at what the encapsulated URL object information looks like.

dubbo://192.168.100.74:20880/com.viewscenes.net supervisor. Service. InfoUserService? Anyhost =true& application = dubbo_producer1 & bind. IP = 192.168.100.74 & bind. The port & dubbo = 2.6.2 & generic = = 20880false& interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserB yId&pid=8596&side=provider&timestamp=1545807234666 rmi://192.168.100.74:1099/com.viewscenes.net supervisor. Service. InfoUserService? Anyhost =true& application = dubbo_producer1 & bind. IP = 192.168.100.74 & bind. The port & dubbo = 2.6.2 & generic = = 1099false& interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserB yId&pid=8596&side=provider&timestamp=1545807267085Copy the code

Before services are exposed, we also have a choice about how we expose them.

  • Local exposure
  • Remote exposed
  • Not to reveal

Their configuration corresponds to the following:

<dubbo:service scope="local" />
<dubbo:service scope="remote" />
<dubbo:service scope="none" />
Copy the code

By default, Dubbo exposes both ways. Local + remote.

private void doExportUrlsFor1Protocol(exporConfig ProtocolConfig, List<URL> URL) {// omitted URL object information process... String scope = url.getParameter(Constants.SCOPE_KEY); // If scope = None, the service is not exposedif(! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) { // scope ! = remote, service exposed locallyif(! Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {exportLocal(url); } // scope ! =local, service exposed to remoteif(! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {if(registryURLs ! = null && ! Registryurls.isempty ()) {// loop the registry for exposure servicesfor (URL registryURL : registryURLs) {
					url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
					URL monitorUrl = loadMonitor(registryURL);
					if(monitorUrl ! = null) { url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString()); }if (logger.isInfoEnabled()) {
						logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry "+ registryURL); } // Generate invoker invoker <? > invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); / / DelegateProviderMetaDataInvoker used to hold the Invoker and ServiceConfig DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); // Invoke the corresponding protocol to expose the service Exporter<? > exporter = protocol.export(wrapperInvoker); exporters.add(exporter); }}else{ Invoker<? > invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<? > exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } } } this.urls.add(url); }Copy the code

3.1. Create Invoker

Before we begin, we must know something else: Invoker. In Dubbo, Invoker is a very important model. Invoker occurs on both the service provider side and the service reference side. The Dubbo website explains it this way:

Invoker is the entity domain that is the core model of Dubbo, to which all other models rely or turn. It represents an executable to which invoke calls can be made. It may be a local implementation, a remote implementation, or a cluster implementation.

In Dubbo, Invoker is created by:

ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class). getAdaptiveExtension(); Invoker<? > invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);Copy the code

First, create an adaptive extension class for the ProxyFactory interface using the extension point loader. In Dubbo, the default is JavassistProxyFactory. So when proxyFactory.getInvoker is called JavassistProxyFactory

public class JavassistProxyFactory extends AbstractProxyFactory {

	public <T> Invoker<T> getInvoker(T proxy, Class<T> typeFinal Wrapper Wrapper = wrapper.getwrapper (proxy.getClass().getName().indexof ())'$') < 0? proxy.getClass() :type); // Create an anonymous Invoker class object and implement itdoInvoke method.return new AbstractProxyInvoker<T>(proxy, type, url) {
			@Override
			protected Object doInvoke(T proxy, String methodName, Class<? >[] parameterTypes, Object[] arguments) throws Throwable {// Call the Wrapper invokeMethod method. InvokeMethod eventually calls the target methodreturnwrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); }}; }}Copy the code

The above method does two main things: create a Wrapper for the target class and implement the doInvoke method.

What is Wrappe?

So where did the Wrapper come from? Up here, we just see it is through

Final Wrapper Wrapper = wrapper.getwrapper is created.

It actually generates this class using the ClassGenerator. The ClassGenerator is wrapped by Dubbo itself. The core of this Class is toClass(ClassLoader, ProtectionDomain), an overloaded method of toClass() that builds the Class using JavAssist. The code involved in the creation process is longer than we need to look at it. Let’s look at what the Wrapper class looks like after it’s generated.

package com.alibaba.dubbo.common.bytecode;

import com.viewscenes.netsupervisor.entity.InfoUser;
import com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;

public class Wrapper1 extends Wrapper implements ClassGenerator.DC {
	public static String[] pns;
	public static Map pts;
	public static String[] mns;
	public static String[] dmns;
	public static Class[] mts0;
	public static Class[] mts1;
	public static Class[] mts2;
	public static Class[] mts3;
	
	public Class getPropertyType(String paramString) {
		return ((Class) pts.get(paramString));
	}
	public String[] getPropertyNames() {
		return pns;
	}
	public Object invokeMethod(Object paramObject, String paramString, Class[] paramArrayOfClass,
			Object[] paramArrayOfObject) throws InvocationTargetException {
		InfoUserServiceImpl localInfoUserServiceImpl;
		try {
			localInfoUserServiceImpl = (InfoUserServiceImpl) paramObject;
		} catch (Throwable localThrowable1) {
			throw new IllegalArgumentException(localThrowable1);
		}
		try {
			if (("getAllUser".equals(paramString)) && (paramArrayOfClass.length == 0))
				return localInfoUserServiceImpl.getAllUser();
			if (("getUserById".equals(paramString)) && (paramArrayOfClass.length == 1))
				return localInfoUserServiceImpl.getUserById((String) paramArrayOfObject[0]);
			if (("insertInfoUser".equals(paramString)) && (paramArrayOfClass.length == 1)) {
				localInfoUserServiceImpl.insertInfoUser((InfoUser) paramArrayOfObject[0]);
				return null;
			}
			if (("deleteUserById".equals(paramString)) && (paramArrayOfClass.length == 1)) {
				localInfoUserServiceImpl.deleteUserById((String) paramArrayOfObject[0]);
				return null;
			}
		} catch (Throwable localThrowable2) {
			throw new InvocationTargetException(localThrowable2);
		}
		throw new NoSuchMethodException("Not found method \"" + paramString
				+ "\" in class com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl.");
	}

	public Object getPropertyValue(Object paramObject, String paramString) {
		InfoUserServiceImpl localInfoUserServiceImpl;
		try {
			localInfoUserServiceImpl = (InfoUserServiceImpl) paramObject;
		} catch (Throwable localThrowable) {
			throw new IllegalArgumentException(localThrowable);
		}
		if (paramString.equals("allUser"))
			return localInfoUserServiceImpl.getAllUser();
		throw new NoSuchPropertyException("Not found property \"" + paramString
				+ "\" filed or setter method in class com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl.");
	}

	public void setPropertyValue(Object paramObject1, String paramString, Object paramObject2) {
		try {
			InfoUserServiceImpl localInfoUserServiceImpl = (InfoUserServiceImpl) paramObject1;
		} catch (Throwable localThrowable) {
			throw new IllegalArgumentException(localThrowable);
		}
		throw new NoSuchPropertyException("Not found property \"" + paramString
				+ "\" filed or setter method in class com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl.");
	}

	public String[] getMethodNames() {
		return mns;
	}

	public String[] getDeclaredMethodNames() {
		return dmns;
	}

	public boolean hasProperty(String paramString) {
		returnpts.containsKey(paramString); }}Copy the code

DoInvoke implementation

Then we look at AbstractProxyInvoker, which is an abstract class that implements the doInvoke method as an anonymous inner class. The wrapper. InvokeMethod method is the final call to the wrapper.invokeMethod method.

The invokeMethod method is eventually executed when the consumer invokes the method. As we saw in the Wrapper above, it calls the corresponding method of the target class (REF) directly based on the argument.

3.2. Local exposure

As we have seen above, if scope! = remote calls local exposure.

if(! Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {exportLocal(url);
}
Copy the code

Local exposure method is relatively simple, first according to the URL protocol header to decide whether to export the service. To export, create a new URL and set the protocol header, host name, and port to the new values. Then create Invoker and call InjvmProtocol’s Export method to export the service.

private void exportLocal(URL URL) {// Determine the protocol headerif(! The LOCAL_PROTOCOL. EqualsIgnoreCase (url. GetProtocol ())) {/ / set the local exposure protocols url urllocal= URL.valueOf(url.toFullString()) .setProtocol(Constants.LOCAL_PROTOCOL) .setHost(LOCALHOST) .setPort(0); ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref)); According to the protocol header, the protocol here will call InjvmProtocol at run timeexportMethods Exporter <? > exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass,local)); exporters.add(exporter); }}Copy the code

As shown in the code above, the benefits of Dubbo SPI’s adaptive feature come when you call protocol.export, automatically retrieving the corresponding extended implementation based on the URL parameter. For example, if this is local exposure, it will call InjvmProtocol. InjvmProtocol’s export method creates only an InjvmExporter, with no other logic. At this point, local exposure is complete.

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
	return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
Copy the code

3.3. Remote exposure

Remote exposure is the same if scope! = local, the method is called. Note that its methods are called inside the registry list loop.

if(! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {if(registryURLs ! = null && ! Registryurls.isempty ()) {// recycle the registryfor(URL registryURL: registryURLs) {// omit irrelevant code... Invoker<? Invoker<? > invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); / / the invoker and encapsulate this object into DelegateProviderMetaDataInvoker DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); // Services exposed to my friend <? > exporter = protocol.export(wrapperInvoker); exporters.add(exporter); }}}Copy the code

To pay attention to the first code: registryURL. AddParameterAndEncoded (” export “, url. ToFullString ()) it the current url information, add into registryURL object, the key for export. When the invoker is obtained, the url header will change to Registry. If the protocol. Export is called, the registryprotocol.export () will be called.

Let’s move on to the Export method of RegistryProtocol.

public <T> Exporter<T> export(final Invoker < T > originInvoker) throws RpcException {/ / exposed services final ExporterChangeableWrapper < T > exporter =doLocalExport(originInvoker); // Obtain the registry URL. Using the ZooKeeper registry as an example, the following example URL is obtained: / / zookeeper: / / 192.168.139.131:2181 / com. Alibaba. Dubbo. Registry. RegistryService... URL registryUrl = getRegistryUrl(originInvoker); // Load the Registry implementation class according to the URL, such as ZookeeperRegistry final Registry = getRegistry(originInvoker); / / get the registered service provider URL, for example: / / dubbo://192.168.100.74:20880/com.viewscenes.net supervisor. Service. InfoUserService... final URL registedProviderUrl = getRegistedProviderUrl(originInvoker); boolean register = registedProviderUrl.getParameter("register".true);
	ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
	if(register) {// Register the service with the registry (registryUrl, registedProviderUrl); ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true); } / / access to subscribe to the URL, for example: / / provider://192.168.100.74:20880/com.viewscenes.net supervisor. Service. InfoUserService... final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl); / / create a listener OverrideListener overrideSubscribeListener = new OverrideListener (overrideSubscribeUrl originInvoker); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); / / to subscribe to override the data to the registry registry. The subscribe (overrideSubscribeUrl overrideSubscribeListener); // Create and return DestroyableExporterreturn new 
	DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
}
Copy the code

In the code above, it does two things:

  • calldoLocalExportService exposure
  • Register services with the registry