This debug code uses the Dubbo – Demo of the Dubbo project on Githubdubbo-demo-xmlThe code below. Here the default Dubbo communication protocol is used as the debug code. In the previous chapter, Spring instantiated the consumer. In this chapter, Spring exposed the Provider service<dubbo:service/>Through theDubbo Tag Parsing in Spring XML FilesThe configuration corresponds toServiceBeanClass, mainly divided into three steps: 1, willdubbo:serviceThe configuration resolves to BeanDefinition 2, and the Spring container passesBeanDefinitionConfiguration instantiationServiceBeanObject 3. Register the service information in the registry and expose it

First post the complete sequence diagram of the whole process:

XML configuration for consumer in demo:

<beans 
       // XMLNS :xsi is the xSI tag namespace
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       // XMLNS :dubbo is the namespace of the dubbo tag
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       // The default namespace for the current XML file
       xmlns="http://www.springframework.org/schema/beans"
       // xsi:schemaLocation configures the configuration specification corresponding to each namespace and is used for format verification
       xsi:schemaLocation="Http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-provider"/>

    <dubbo:registry address=Zookeeper: / / "127.0.0.1:2181" timeout="6000"/ > <! <dubbo:protocol name="dubbo"/>
    
    <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>

    <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" timeout="60000"/>

</beans>
Copy the code

1, will bedubbo:serviceThe configuration resolves to BeanDefinition

Dubbo’s consumer configuration is < Dubbo: Reference >, which can be seen in Spring parsing the Dubbo tag configuration as BeanDefinition. Through DubboNamespaceHandler parse method resolution dubbo custom tag, to the Spring container to register a listener DubboLifecycleComponentApplicationListener, This class listens for the Spring container’s ApplicationEvent event, which triggers the process exposed by the service. The code to register the listener is as follows:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    BeanDefinitionRegistry registry = parserContext.getRegistry();
    this.registerAnnotationConfigProcessors(registry);
    // Register listeners with the Spring container
    this.registerDubboLifecycleComponentApplicationListener(registry);
    BeanDefinition beanDefinition = super.parse(element, parserContext);
    this.setSource(beanDefinition);
    return beanDefinition;
}
Copy the code

The sequence diagram is as follows:

2. The Spring container passesBeanDefinitionConfiguration instantiationServiceBeanobject

inSpring dubbo tag Configuration processAs can be seen fromdubbo:serviceConfigure the correspondingBeanDefinitionThe BeanClass attribute oforg.apache.dubbo.config.spring.ServiceBean.classClass, will be passed by SpringgetBean()Instantiate (getBean()For detailed logic, seehere), the debug screenshot is as follows:

ServiceBean extends AbstractConfig. This abstract class has an addIntoConfigManager method that caches the current object into a global ConfigManager object:

    @PostConstruct
    public void addIntoConfigManager(a) {
        ApplicationModel.getConfigManager().addConfig(this);
    }
Copy the code

The @postConstruct annotation tells the Spring container to execute the annotated methods after instantiating the object. Almost all dubbo configuration classes inherit AbstractConfig. For example,

RegistryConfig,

ReferenceBean will be put into the global cache dubbobootstrap.start () will read this cache. This triggers logic such as registries and exposure services.

3, listen to the Spring container event, triggerDubboBootstrap.start()Method to start the service exposure logic

Exposed service entrance atDubboLifecycleComponentApplicationListener.onApplicationEvent(ApplicationEvent event)Method, call the sequence diagram as follows:

DubboLifecycleComponentApplicationListener. OnApplicationEvent () code:

 public void onApplicationEvent(ApplicationEvent event) {
        if (this.supportsEvent(event)) {
            if (event instanceof ContextRefreshedEvent) {
                this.onContextRefreshedEvent((ContextRefreshedEvent)event);
            } else if (event instanceof ContextClosedEvent) {
                this.onContextClosedEvent((ContextClosedEvent)event); }}}protected void onContextRefreshedEvent(ContextRefreshedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        DubboBootstrap bootstrap = this.loadBootsttrapAsBean(context);
        if (bootstrap == null) {
        // Create DubboBootstrap in singleton mode
            bootstrap = DubboBootstrap.getInstance();
        }
        / / 1.0
        bootstrap.start();
    }
Copy the code

The start method code is as follows:

public DubboBootstrap start(a) {
        if (this.started.compareAndSet(false.true)) {
            // Initialize the configuration, including the registry
            this.initialize();
            if (this.logger.isInfoEnabled()) {
                this.logger.info(NAME + " is starting...");
            }
            // Expose service
            this.exportServices();
            if (!this.isOnlyRegisterProvider() || this.hasExportedServices()) {
                this.exportMetadataService();
                this.registerServiceInstance();
            }
            // Parse and register the consumer configuration
            this.referServices();
            if (this.logger.isInfoEnabled()) {
                this.logger.info(NAME + " has started."); }}return this;
    }
Copy the code

4, according to theServiceBeanConfigure service exposure

In step 2, the configuration is parsed into a ServiceBean and cached in the global ConfigManager object, where the configuration is fetched from the global object and service exposed one by one, with the following trunk code:

private void exportServices(a) {
        this.configManager.getServices().forEach((sc) -> {
            ServiceConfig serviceConfig = (ServiceConfig)sc;
            serviceConfig.setBootstrap(this); . sc.export();this.exportedServices.add(sc);
        });
 }
Copy the code

4.1 Obtaining Registry Configuration and Protocol Configuration

Following the code above, Dubbo supports a variety of protocols, including the default Dubbo protocol, Hessian, HTTP, thrift, etc. The differences between protocols can be searched for themselves.

public synchronized void export(a) {
  if (this.shouldExport()) {
      if (this.bootstrap == null) {
          this.bootstrap = DubboBootstrap.getInstance();
          this.bootstrap.init();
      }
      // Check and modify sub-configurations, including checking registry addresses, protocol configurations, etc
      this.checkAndUpdateSubConfigs(); .this.doExport(); }}Copy the code

Check the configuration source code as follows:

private void checkAndUpdateSubConfigs(a) {...// Check the protocol configuration, if not use the default dubbo, and set the ProtocolConfig configuration to the Protocols property
    this.checkProtocol();
    // Check the registry configuration and set the RegistryConfig object to the registries property
    if (!this.isOnlyInJvm()) {
        this.checkRegistry(); }... }Copy the code

The configuration here is taken from the global ConfigManager object and put into the cache after all the configuration is initialized by Spring. The principle has been described above.

private void doExportUrls(a) {
    ServiceRepository repository = ApplicationModel.getServiceRepository();
    ServiceDescriptor serviceDescriptor = repository.registerService(this.getInterfaceClass());
    repository.registerProvider(this.getUniqueServiceName(), this.ref, serviceDescriptor, this.this.serviceMetadata);
    // Get the registry address
    List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this.true);
    // Protocol capacity configured
    Iterator var4 = this.protocols.iterator();
    
    while(var4.hasNext()) { ProtocolConfig protocolConfig = (ProtocolConfig)var4.next(); .// Handle subsequent exposure logic with registry configuration and protocol configuration
        this.doExportUrlsFor1Protocol(protocolConfig, registryURLs); }}Copy the code

4.2 Generate proxy classes dynamically and wrap them into invoker chains based on exposed beans

The doExportUrlsFor1Protocol method is called, and I retain the trunk code as follows:

private static final ProxyFactory PROXY_FACTORY = (ProxyFactory)ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs){...// 1 Use ProxyFactory to generate the proxy class to construct the invoker chainInvoker<? > invoker = PROXY_FACTORY.getInvoker(this.ref, this.interfaceClass, registryURL.addParameterAndEncoded("export", url.toFullString()));
  // The invoke method inside the wrapper class does nothing but forward logic
  DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
  // 2 Invoke the export exposure service with the wrapped Invoker argumentExporter<? > exporter = protocol.export(wrapperInvoker);this.exporters.add(exporter);
}
Copy the code

PROXY_FACTORY is an implementation class extended by SPI, inHow to register Dubbo Consumer with SpringSPI extends three classes: adapter class -> wrapper class -> implementation class. The actual return is an adapter class.

The Wrapper class is dynamically generated and its invokeMethod does only layer verification. Arthas decomcompiled the following code:

    public Object invokeMethod(Object object, String string, Class[] classArray, Object[] objectArray) throws InvocationTargetException {
        DemoServiceImpl demoServiceImpl;
        try {
            demoServiceImpl = (DemoServiceImpl)object;
        }
        catch (Throwable throwable) {
            throw new IllegalArgumentException(throwable);
        }
        try {
            if ("sayHello".equals(string) && classArray.length == 1) {
                return demoServiceImpl.sayHello((String)objectArray[0]);
            }
            if ("sayHelloAsync".equals(string) && classArray.length == 1) {
                return demoServiceImpl.sayHelloAsync((String)objectArray[0]); }}catch (Throwable throwable) {
            throw new InvocationTargetException(throwable);
        }
        throw new NoSuchMethodException(new StringBuffer().append("Not found method \"").append(string).append("\" in class org.apache.dubbo.demo.provider.DemoServiceImpl.").toString());
    }
Copy the code

A chain of invoker calls is formed by wrapping the class. The final invoker chain is as follows:

4.3 Enabling Socket Services using the Netty Framework

Then 4.2 source code, with 4.2 wrapped invoker as the argument, callExporter<? > exporter = protocol.export(wrapperInvoker)Method, right hereprotocolIs a class extended by SPI, since the protocol here isregistryThe RegistryProtocol class is called.

ProviderUrl contains a lot of metadata information, such as version, group, etc. It overwrites these information according to the configuration in the configuration center to prepare for the subsequent service exposure. When the configuration center is not configured, Dubbo uses the registry as the configuration center by default. My registry is ZooKeeper, so I’ll use ZooKeeper as the configuration center

public <T> Exporter<T> export(Invoker<T> originInvoker) throws RpcException {
        // Get the registry URL
        URL registryUrl = this.getRegistryUrl(originInvoker);
        // Get the PROVIDER URL
        URL providerUrl = this.getProviderUrl(originInvoker);
        URL overrideSubscribeUrl = this.getSubscribedOverrideUrl(providerUrl);
        RegistryProtocol.OverrideListener overrideSubscribeListener = new RegistryProtocol.OverrideListener(overrideSubscribeUrl, originInvoker);
        this.overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        // Modify the parameter information in providerUrl according to the configuration center configuration
        providerUrl = this.overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
        // Expose service
        RegistryProtocol.ExporterChangeableWrapper<T> exporter = this.doLocalExport(originInvoker, providerUrl);
        // 4.3.2 Registering a service to a registry
        Registry registry = this.getRegistry(originInvoker);
        URL registeredProviderUrl = this.getUrlToRegistry(providerUrl, registryUrl);
        boolean register = providerUrl.getParameter("register".true);
        if (register) {
            this.register(registryUrl, registeredProviderUrl);
        }
        // Subscribe to the Registry's Configurators node information and register listeners for real-time callbacks
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        exporter.setRegisterUrl(registeredProviderUrl);
        exporter.setSubscribeUrl(overrideSubscribeUrl);
        return new RegistryProtocol.DestroyableExporter(exporter);  
      
    }
Copy the code

Then the this.dolocalexport (originInvoker, providerUrl) code above:

    
    private <T> RegistryProtocol.ExporterChangeableWrapper<T> doLocalExport(Invoker<T> originInvoker, URL providerUrl) {
        String key = this.getCacheKey(originInvoker);
        return (RegistryProtocol.ExporterChangeableWrapper)this.bounds.computeIfAbsent(key, (s) -> {
            // Wrap invoker as an InvokerDelegate objectInvoker<? > invokerDelegate =new RegistryProtocol.InvokerDelegate(originInvoker, providerUrl);
            // Expose the service and wrap the returned Exporter, focusing on this.protocol.export(invokerDelegate)
            return new RegistryProtocol.ExporterChangeableWrapper(this.protocol.export(invokerDelegate), originInvoker);
        });
    }
    
    private static finalProtocol protocol = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); Exporter<? > exporter = protocol.export(wrapperInvoker);Copy the code

Protocol is a class extended through SPI, as described above. Except that the protocol in the URL here becomes dubbo, so the export method of the DubboProtocol class is finally called as follows:

public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();
        // Wrap the invoker and key as a single exporter and cache it in a Map. When a request is received, the Map matches the corresponding Invoker
        String key = serviceKey(url);
        DubboExporter<T> exporter = new DubboExporter(invoker, key, this.exporterMap);
        this.exporterMap.put(key, exporter);
        Boolean isStubSupportEvent = url.getParameter("dubbo.stub.event".false);
        Boolean isCallbackservice = url.getParameter("is_callback_service".false);
        if(isStubSupportEvent && ! isCallbackservice) { String stubServiceMethods = url.getParameter("dubbo.stub.event.methods");
            if(stubServiceMethods ! =null&& stubServiceMethods.length() ! =0) {
                this.stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            } else if (this.logger.isWarnEnabled()) {
                this.logger.warn(new IllegalStateException("consumer [" + url.getParameter("interface") + "], has set stubproxy support event ,but no stub methods founded.")); }}// Start the ServerSocket service
        this.openServer(url);
        this.optimizeSerialization(url);
        return exporter;
    }
Copy the code

There are two main functions of this method:

  1. The key is composed of the class name, port, version number, and group in the Url. The Exporter that encapsulates the Invoker information is referred to as value and cached toDubboProtocolOf the classexporterMapDuring the subsequent processing of consumser request, this Map will be used to match the corresponding Invoker to process the request.serviceKey(URL url)The code is as follows:
protected static String serviceKey(URL url) {
        int port = url.getParameter("bind.port", url.getPort());
        return serviceKey(port, url.getPath(), url.getParameter("version"), url.getParameter("group"));
    }
Copy the code
  1. Initialize a NettyServer via Netty, and start ServerSocket service, listen for port events, ready to receive requests, code as follows:
private void openServer(URL url) {
        String key = url.getAddress();
        boolean isServer = url.getParameter("isserver".true);
        if (isServer) {
            ProtocolServer server = (ProtocolServer)this.serverMap.get(key);
            if (server == null) {
                synchronized(this) {
                    server = (ProtocolServer)this.serverMap.get(key);
                    if (server == null) {
                    // Create the Server and cache the Server in the serverMap
                        this.serverMap.put(key, this.createServer(url)); }}}else{ server.reset(url); }}}Copy the code

this.serverMap.put(key, this.createServer(url)); Finally call nettytransporter. bind(URL URL, ChannelHandler listener) :

    public RemotingServer bind(URL url, ChannelHandler listener) throws RemotingException {
        return new NettyServer(url, listener);
    }
Copy the code

In the NettyServer constructor, Socket listening is enabled and the thread pool is initialized according to the configuration as follows:

public AbstractServer(URL url, ChannelHandler handler) throws RemotingException {
        super(url, handler);
        String bindIp = this.getUrl().getParameter("bind.ip".this.getUrl().getHost());
        int bindPort = this.getUrl().getParameter("bind.port".this.getUrl().getPort());
        if (url.getParameter("anyhost".false) || NetUtils.isInvalidLocalHost(bindIp)) {
            bindIp = "0.0.0.0";
        }

        this.bindAddress = new InetSocketAddress(bindIp, bindPort);
        this.accepts = url.getParameter("accepts".0);
        this.idleTimeout = url.getParameter("idle.timeout".600000);

        try {
        // Enable the Socket service
            this.doOpen();
            if (logger.isInfoEnabled()) {
                logger.info("Start " + this.getClass().getSimpleName() + " bind " + this.getBindAddress() + ", export " + this.getLocalAddress()); }}catch (Throwable var6) {
            throw new RemotingException(url.toInetSocketAddress(), (InetSocketAddress)null."Failed to bind " + this.getClass().getSimpleName() + " on " + this.getLocalAddress() + ", cause: " + var6.getMessage(), var6);
        }
        // Initialize thread pools according to the SPI extension mechanism
        this.executor = this.executorRepository.createExecutorIfAbsent(url);
    }
Copy the code

The resulting call chain is as follows:

4.4 Publishing The Provider information to the Registry

  this.register(registryUrl, registeredProviderUrl);
        
  public void register(URL registryUrl, URL registeredProviderUrl) {,// Get the registry
    Registry registry = this.registryFactory.getRegistry(registryUrl);
    // Publish the service provider to the registry. The underlying implementation is registered using the ZooKeeper client curator
    registry.register(registeredProviderUrl);
    ProviderModel model = ApplicationModel.getProviderModel(registeredProviderUrl.getServiceKey());
    model.addStatedUrl(new RegisterStatedURL(registeredProviderUrl, registryUrl, true));
  }
Copy the code

this.registryFactoryIs extended through the SPI mechanism, and returns an adapter class that will be based onregistryUrlProtocol header to determine which registry object to return.

This is where the service exposure logic of Dubbo’s service provider ends, with extensive use of the design pattern of wrapped classes, invocation chains, and extended functionality via SPI.