Local exposure of dubbo services

Write in front

The Service provided by the Service provider is marked with a class annotated with @ Service. If you want to be used by the Service consumer, you must expose the Service, that is, let the Service consumer get the com that encapsulates the Service information alibaba. dubbo. common. URL object string. The current Service is exposed in the following three ways:

Remote exposure: register the service information to the remote registry, such as configuration<dubbo:service scope="remote" />. 
Local exposure: JVM Internal call. Because the information is already in memory, the call information can be obtained directly through memory, so it is called local exposure, such as configuration<dubbo:service scope="local">. 
Do not expose: do not expose services. This method can be ignored, such as configuration<dubbo:service scope="none">. 

This article shares the local exposure. The relevant source code is in the module Dubbo RPC injmv, as shown in the following figure:

stay dubbo service provider configuration In this article, we actually analyzed the contents exposed by some services. You can see that in order to undertake this article, there will be some overlapping contents. Let's start with method com alibaba. dubbo. config. ServiceConfig. Doexporturls to start analysis.

1: doExportUrls

The source code is as follows:

class FakeCls {
    private void doExportUrls() {
        // 2022-01-21 18:25:43
        List<URL> registryURLs = loadRegistries(true);
        // Loop all protocol exposed services to all registry addresses
        // Protocol: protocols, i.e. < Dubbo: Protocol > Settings
        // Service: set through < Dubbo: Service >
        // Registry address: registryURLs, specified by < Dubbo: Registry >
        for (ProtocolConfig protocolConfig : protocols) {
            // 2022-01-21 18:34:22
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }
}

Get the addresses of all configured registries at 18:25:43 on January 21, 2022. Refer to 1.1: loadRegistries for details. At 18:34:22 on January 21, 2022, the service is registered in the registry according to the specified agreement. For details, refer to 1.2: doExportUrlsFor1Protocol.

1.1: loadRegistries

The local service is leaked during the explanation of this article, and the information here will not be used. However, for the sake of integrity, it is put here. Friends who are interested in this part can refer to it dubbo's service remote exposure Article analysis.

1.2: doExportUrlsFor1Protocol

The service is registered in the registry according to the specified protocol, which is divided into remote disclosure and local disclosure. The local disclosure will not register the service in the registry. Because it is the same JVM, the information can be obtained directly from the JVM. Because this paper focuses on the local service disclosure, the relevant source code of remote disclosure will be selectively ignored. For the analysis of this part, Can only refer to dubbo's service remote exposure Article analysis. The source code is as follows:

class FakeCls {
    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        // The name of the protocol, such as < dubbo: protocol name = "dubbo" port = "20826" >, here is dubbo
        // Agreement: is to reveal your own way
        String name = protocolConfig.getName();
        // If not, dubbo is used by default
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }
        //***Omit code related to building URL***//
        // Get the scope. If it is a local burst, configure it as follows: < Dubbo: service interface = "Dongshi. Daddy. Service. Scopelocal. Scopelocal service" ref = "scopelocal service" scope = "local" / >
        // It is also mentioned at the beginning of the article that it can be configured as remote, which represents remote disclosure, and none represents non disclosure
        String scope = url.getParameter(Constants.SCOPE_KEY);
        // If scope="none" is configured, no operation will be carried out. At this time, no burst leakage will be carried out, that is, it will not be used externally
        if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
            // If the scope is not remote, the local service is used
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                // 2022-01-22 12:25:51
                exportLocal(url);
            }
            // If the scope is not local, the remote service is used
            if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
                // ***Omit remote service burst logic * * *//
            }
        }
        // Add leak service url
        this.urls.add(url);
    }
}

At 12:25:51 on January 22, 2022, there was a local service leak. Refer to 1.3: exportLocal for details.

1.3: exportLocal

The source code is as follows:

class FakeCls {
    private void exportLocal(URL url) {
        // url.getProtocol(): usually dubbo 
        // Constants.LOCAL_PROTOCOL: injvm
        // Why do you make this judgment???
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
            // Build the URL of local, such as
            // injvm://127.0.0.1/dongshi.daddy.service.scopelocal.ScopeLocalService?accesslog=true&anyhost=true&application=dongshidaddy-provider&bean.name=dongshi.daddy.service.scopelocal.ScopeLocalService&bind.ip=192.168.2.107&bind.port=20826&dubbo=2.0.2&generic=false&interface=dongshi.daddy.service.scopelocal.ScopeLocalService&methods=sayHi&owner=dongshidaddy&pid=6324&scope=local&side=provider&timestamp=1642823993714
            URL local = URL.valueOf(url.toFullString())
                    .setProtocol(Constants.LOCAL_PROTOCOL)
                    .setHost(LOCALHOST)
                    .setPort(0);
            // public static final String SERVICE_IMPL_CLASS = "service.classimpl";
            // url.getServiceKey(): dongshi.daddy.service.scopelocal.ScopeLocalService
            // getServiceClass(ref: class dongshi.daddy.service.scopelocal.ScopeLocalServiceImpl
            // Store the information of the service class in the StaticContext
            StaticContext.getContext(Constants.SERVICE_IMPL_CLASS).put(url.getServiceKey(), getServiceClass(ref));
            // 2022-01-22 17:21:22
            Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
            exporters.add(exporter);
            logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
        }
    }
}

The following part of this section is a little around. Everyone is patient. If you don't understand, read it several times!!!

At 17:21:22 on January 22, 2022, the protocol is defined as private static final protocol protocol = extensionloader getExtensionLoader(Protocol.class). getAdaptiveExtension();, You can see that it is acquisition Adaptive extension class , which can also be seen from the Protocol interface. The source code is as follows:

/**
 * Protocol. (API/SPI, Singleton, ThreadSafe)
 */
@SPI("dubbo")
public interface Protocol {
    int getDefaultPort();
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
    void destroy();
}

You can see that the export method is labeled @Adaptive Annotated, here 'protocol'
It is Protocol#Adaptive, which is easy to understand, because acquisition is a dynamically generated adaptive subclass through which the final call is implemented by a real extension class. If you want to know who the call is, you need to know what the generated code looks like. We can obtain its contents through the following steps:

stay com.alibaba.dubbo.common.extension.ExtensionLoader.createAdaptiveExtensionClass Code in ClassLoader classLoader = findClassLoader();Add condition variable"code.contains("Protocol$Adaptive")",Then run the program again, you can stop here and get code The content of.

Here's what I got:

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

We focus on the export method. We can see that it calls url Getprotocol () is the name of the target extension class, so what is the value? Our url is injvm://127.0.0.1/... You can see that the protocol is injvm. Who is the corresponding extension class? You can find it from the file meta-inf / Dubbo / internal / com alibaba. dubbo. rpc. Find the answer in the protocol, where the key is injvm, and the configuration item content is injvm = com alibaba. dubbo. rpc. protocol. injvm. Injvmprotocol, so the extension class finally called is com alibaba. dubbo. rpc. protocol. injvm. Injvmprotocol, but is that true? Let's take a look at debug, as shown below:

As can be seen from the figure, QosProtocolWrapper, protocollistenerwrapper and protocolfilterwrapper are also called respectively. This is the Wrapper class of Protocol. We start from meta-inf / Dubbo / internal / com alibaba. dubbo. rpc. It can be seen from the Protocol, as shown in the following figure:

For more information about Wrapper, please refer to SPI Wrapper analysis of dubbo .

The final calling procedure is Protocol $adaptive - > qosprotocolwrapper - > protocollistenerwrapper - > protocolfilterwrapper - > injvmprotocol. Specifically, we analyze it in 2: Protocol.

2: Protocol

The source code is as follows:

@SPI("dubbo")
public interface Protocol {

    // Gets the default port number of the current protocol when no port is configured
    int getDefaultPort();

    // The burst service can be called remotely
    // 1: The protocol object needs to record the address of the remote source after receiving a request through API rpccontext getContext(). setRemoteAddress()
    // 2: The method must be idempotent [a ɪ' demp ə t ə nt]), that is, there is no difference in exposing a URL through one or more calls of this method
    // 3: The Invoker instance needs to be passed in by the framework, and the protoco l extension class needs to be used, such as when adaptive
    // Return value: exporter < T >, which refers to the leaked service. Later, if you need to cancel the disclosure, you need to use it
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

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

    void destroy();
}

2.1: Protocol$Adaptive

Refer to 1.3: exportLocal for how to obtain such information.

public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        // Because there is no @ Adaptive annotation, Java. XML is thrown directly lang.UnsupportedOperationException
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
    
    // Because there is no @ Adaptive annotation, Java. XML is thrown directly lang.UnsupportedOperationException
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

2.2: ProtocolListenerWrapper

The source code is as follows:

class FakeCls {
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        // The following remote bursts will be executed, which can be ignored here, because the url starts with injvm: / /
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        // 2022-01-23 19:29:28
        return new ListenerExporterWrapper<T>(protocol.export(invoker),
                Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                        .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
    }
}

2022-01-23 19:29:28 protocol Export (invoker) continues to call the decorated protocol class. What is called here is ProtocolFilterWrapper. For this class, refer to 2.3: ProtocolFilterWrapper. Collections. unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class). getActivateExtension(invoker.getUrl(), Constants. EXPORTER_ LISTENER_ Key) is to use keyexporter Listener, get the value from the url to get the exporterlistener extension class to activate. The source code of the ListenerExporterWrapper constructor is as follows:

class FakeCls {
    public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) {}
}

The function of this listener is to monitor the completion of the Exporter's burst and cancel the burst.

2.3: ProtocolFilterWrapper

It is mainly used to add a Filter chain to the Invoker. The logic of the Filter will be called before calling the real service method. For details, refer to 2.3.1: export.

2.3.1: export

The source code is as follows:

class FakeCls {
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        // The protocol required here is registry: / /, that is, remote exposure, and injvm: / /, so it can be ignored
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        // 2022-01-24 16:02:53
        // The protocol here is InJvmProtocol
        return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }

}

Add Filter chain at buildInvokerChain at 16:02:53 on January 24, 2022. Refer to 2.3.2: buildInvokerChain for details.

2.3.2: buildInvokerChain

The source code is as follows:

class FakeCls {
    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        // Gets the collection of activated Filter extension classes
        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;
                // Encapsulate the Filter into an Invoker. When invoking the invoke method, the next Invoker will be called in an internal chain. The last Invoker is the Invoker of the target service class method
                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 {
                        // This line of code is key. Call the Filter class method with next as a parameter. Inside the Filter class method, we can use invoker Invoke to continue to call down, and finally call to the real service class method
                        return filter.invoke(next, invocation);
                    }

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

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

The later filters are executed first and first, such as Filter1 - > filte2 - > Filter3 - >... - > Service class methods.

2.4: InjvmProtocol

This class is the implementation class of the Injvm protocol. Let's start with the entry method export.

2.4.1: export

The source code is as follows:

class FakeCls {
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
    }
}

Mainly created the InjvmExporter object. For details about this object, refer to 3: Exporter.

3: Exporter

The interface is used to expose services based on relevant protocols. The interface source code is as follows:

public interface Exporter<T> {
    // Get internal Invoker
    Invoker<T> getInvoker();
    // Cancel exposure
    void unexport();
}

The main class diagrams are as follows:

Next, let's start with the class AbstractExporter and look at the following.

3.1: AbstractExporter

The source code is as follows:

public abstract class AbstractExporter<T> implements Exporter<T> {

    protected final Logger logger = LoggerFactory.getLogger(getClass());
    // Internal Invoker
    private final Invoker<T> invoker;
    // Are there no exposed marks
    private volatile boolean unexported = false;

    public AbstractExporter(Invoker<T> invoker) {
        if (invoker == null)
            throw new IllegalStateException("service invoker == null");
        // Must be an interface
        if (invoker.getInterface() == null)
            throw new IllegalStateException("service type == null");
        // There must be an exposed URL
        if (invoker.getUrl() == null)
            throw new IllegalStateException("service url == null");
        this.invoker = invoker;
    }

    @Override
    public Invoker<T> getInvoker() {
        return invoker;
    }

    // To cancel exposure is to call getinvoker() destroy();
    @Override
    public void unexport() {
        if (unexported) {
            return;
        }
        unexported = true;
        getInvoker().destroy();
    }

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

3.2: InjvmExporter

Subclass of AbstractExporter. The source code is as follows:

class InjvmExporter<T> extends AbstractExporter<T> {
    // The service key is generally the fully qualified class name of the service interface
    private final String key;
    // Exposed exporters
    private final Map<String, Exporter<?>> exporterMap;

    InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
        super(invoker);
        this.key = key;
        this.exporterMap = exporterMap;
        exporterMap.put(key, this);
    }
    
    // Cancel exposure
    @Override
    public void unexport() {
        super.unexport();
        exporterMap.remove(key);
    }
}

3.3 ListenerExporterWrapper

The Wrapper class of Exporter with listening function has the following source code:

public class ListenerExporterWrapper<T> implements Exporter<T> {

    private static final Logger logger = LoggerFactory.getLogger(ListenerExporterWrapper.class);

    private final Exporter<T> exporter;
    // Registered exposure listener
    private final List<ExporterListener> listeners;

    public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) {
        if (exporter == null) {
            throw new IllegalArgumentException("exporter == null");
        }
        this.exporter = exporter;
        this.listeners = listeners;
        // Constructor execution represents that the service is exposed, and the exposed method of the corresponding listener is exported
        if (listeners != null && !listeners.isEmpty()) {
            RuntimeException exception = null;
            for (ExporterListener listener : listeners) {
                if (listener != null) {
                    try {
                        listener.exported(this);
                    } catch (RuntimeException t) {
                        logger.error(t.getMessage(), t);
                        exception = t;
                    }
                }
            }
            if (exception != null) {
                throw exception;
            }
        }
    }

    @Override
    public Invoker<T> getInvoker() {
        return exporter.getInvoker();
    }

    @Override
    public void unexport() {
        // Cancel the exposure and execute the listener's unexported method
        try {
            exporter.unexport();
        } finally {
            if (listeners != null && !listeners.isEmpty()) {
                RuntimeException exception = null;
                for (ExporterListener listener : listeners) {
                    if (listener != null) {
                        try {
                            listener.unexported(this);
                        } catch (RuntimeException t) {
                            logger.error(t.getMessage(), t);
                            exception = t;
                        }
                    }
                }
                if (exception != null) {
                    throw exception;
                }
            }
        }
    }
}

ExporterListener reference 4: ExporterListener.

4: ExporterListener

The source code is as follows:

@SPI
public interface ExporterListener {
    // Method invoked by service exposure
    void exported(Exporter<?> exporter) throws RpcException;
    // Service unexposed method invoked
    void unexported(Exporter<?> exporter);
}

Class diagram is as follows:

Next, take a look at the unique implementation class ExporterListenerAdapter, as follows:

public abstract class ExporterListenerAdapter implements ExporterListener {

    @Override
    public void exported(Exporter<?> exporter) throws RpcException {
    }

    @Override
    public void unexported(Exporter<?> exporter) throws RpcException {
    }

}

It is just an empty implementation without actual logic.

Keywords: Dubbo

Added by ejames13 on Mon, 24 Jan 2022 20:36:05 +0200