The Dubbo service references the process and implementation details welcome thumbs up, thumbs up, thumbs up.

Follow my public account Solomon for more highlights

Process and implementation details for Dubbo service references

1, the introduction of

In Dubbo, we can refer to remote services in two ways. The first is to reference a service directly, and the second is to reference it based on a registry. Direct service connection is only suitable for commissioning or testing services, not for online environments. Therefore, in this article I will focus on the process of referencing services through a registry. Getting the service configuration from the registry is just one step in the service reference process. Besides, the service consumer also needs to go through the Invoker creation, proxy class creation, and so on.

2. Principle of service reference

The Dubbo service can be referenced when the Spring container calls the afterPropertiesSet method of the ReferenceBean, or when the corresponding service of the ReferenceBean is injected into another class. The difference in timing between these two reference services is that the first is hungrier and the second is slacker. By default, Dubbo uses the lazy reference service. If you need to use the hanhan-type function, configure the init property of Dubbo :reference to enable it. Let’s do the analysis with the default Dubbo configuration, starting with the getObject method of the ReferenceBean. When our service is injected into another class, Spring first calls the getObject method, which performs the service reference logic. As a rule, configuration check and collection should be performed before any specific work. The collected information is then used to determine how the service will be used. There are three ways to reference a local (JVM) service, a direct connection to a remote service, and a registry to reference a remote service. Either way, you end up with an Invoker instance. If you have multiple registries and multiple service providers, you will get a set of Invoker instances, and you will need to merge these invokers into one instance through the Cluster management class Cluster. The merged Invoker instance already has the ability to invoke local or remote services, but it cannot be exposed to the user for use, which would be intrusive to the user’s business code. At this point, the framework also needs to generate proxy classes for the service interface through the ProxyFactory class (ProxyFactory) and let the proxy classes invoke the Invoker logic. It avoids the intrusion of Dubbo framework code into business code and also makes the framework easier to use.

3. Source code analysis

The entry method to the service reference is the ReferenceBean’s getObject method, which is defined in Spring’s FactoryBean interface and implemented by ReferenceBean. The implementation code is as follows:

public Object getObject(a) throws Exception {
    return get();
}

public synchronized T get(a) {
    if (destroyed) {
        throw new IllegalStateException("Already destroyed!");
    }
    // Check if ref is empty and init if it is
    if (ref == null) {
        The init method is mainly used to handle configuration and createProxy classes by calling createProxy
        init();
    }
    return ref;
}
Copy the code

The code for the above two methods is short and not hard to understand. It is important to note that if you are debugging getObject 2.6.4 and below, you will encounter some strange problems. This assumes that you use IDEA and keep the default configuration of IDEA. If (ref == null) of the get method, you will find that ref is not null, so you can’t go into the init method to continue debugging. The reason for this is that the Dubbo framework itself has some minor issues. The issue was fixed in Pull Request #2754 and released with version 2.6.5. If you are learning 2.6.4 or later, you can circumvent this problem by modifying the IDEA configuration. First search for toString in the IDEA configuration popup and then uncheck Enable ‘toString’ Object view. Details are as follows:

! [img] (Dubbo service reference and the process of the implementation details. Assets / 15417503733794 JPG)

3.1 Handling the Configuration

Dubbo provides rich configurations for tuning and optimizing framework behavior, performance, and more. Dubbo checks and processes these configurations before referencing or exporting services to ensure correct configurations. The configuration parsing logic is encapsulated in the init method of ReferenceConfig, which is analyzed below.

private void init(a) {
    // Avoid repeated initialization
    if (initialized) {
        return;
    }
    initialized = true;
    // Check the validity of the interface name
    if (interfaceName == null || interfaceName.length() == 0) {
        throw new IllegalStateException("interface not allow null!");
    }

    // Test if the consumer variable is empty
    checkDefault();
    appendProperties(this);
    if (getGeneric() == null&& getConsumer() ! =null) {
        / / set the generic
        setGeneric(getConsumer().getGeneric());
    }

    // Check if it is a generalized interface
    if (ProtocolUtils.isGeneric(getGeneric())) {
        interfaceClass = GenericService.class;
    } else {
        try {
            / / load the classes
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        checkInterfaceAndMethods(interfaceClass, methods);
    }
    
    / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ✨ ✨ line 1 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

    // Get the attribute value corresponding to the interface name from the system variable
    String resolve = System.getProperty(interfaceName);
    String resolveFile = null;
    if (resolve == null || resolve.length() == 0) {
        // Get the parse file path from system properties
        resolveFile = System.getProperty("dubbo.resolve.file");
        if (resolveFile == null || resolveFile.length() == 0) {
            // Loads the configuration file from the specified location
            File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
            if (userResolveFile.exists()) {
                // Get the absolute file pathresolveFile = userResolveFile.getAbsolutePath(); }}if(resolveFile ! =null && resolveFile.length() > 0) {
            Properties properties = new Properties();
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(new File(resolveFile));
                // Load the configuration from a file
                properties.load(fis);
            } catch (IOException e) {
                throw new IllegalStateException("Unload ... , cause:...");
            } finally {
                try {
                    if (null! = fis) fis.close(); }catch(IOException e) { logger.warn(e.getMessage(), e); }}// Get the configuration corresponding to the interface nameresolve = properties.getProperty(interfaceName); }}if(resolve ! =null && resolve.length() > 0) {
        // Assign resolve to the URL
        url = resolve;
    }
    
    / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ✨ ✨ line 2 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    if(consumer ! =null) {
        if (application == null) {
            // Get the Application instance from consumer, same as below
            application = consumer.getApplication();
        }
        if (module= =null) {
            module = consumer.getModule();
        }
        if (registries == null) {
            registries = consumer.getRegistries();
        }
        if (monitor == null) { monitor = consumer.getMonitor(); }}if (module! =null) {
        if (registries == null) {
            registries = module.getRegistries();
        }
        if (monitor == null) {
            monitor = module.getMonitor(); }}if(application ! =null) {
        if (registries == null) {
            registries = application.getRegistries();
        }
        if (monitor == null) { monitor = application.getMonitor(); }}// Check Application validity
    checkApplication();
    // Check the validity of the local stub configuration
    checkStubAndMock(interfaceClass);
    
	/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - 3 ✨ ✨ segmentation -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    
    Map<String, String> map = new HashMap<String, String>();
    Map<Object, Object> attributes = new HashMap<Object, Object>();

    // Add side, protocol version information, timestamp, and process number to map
    map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
    map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
    if (ConfigUtils.getPid() > 0) {
        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
    }

    // Non-generic services
    if(! isGeneric()) {// Get the version
        String revision = Version.getVersion(interfaceClass, version);
        if(revision ! =null && revision.length() > 0) {
            map.put("revision", revision);
        }

        // Get the list of interface methods and add them to the map
        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
        if (methods.length == 0) {
            map.put("methods", Constants.ANY_VALUE);
        } else {
            map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
        }
    }
    map.put(Constants.INTERFACE_KEY, interfaceName);
    // Add the fields of ApplicationConfig, ConsumerConfig, ReferenceConfig and other objects to the map
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, consumer, Constants.DEFAULT_KEY);
    appendParameters(map, this);
    
	/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - 4 ✨ ✨ rules -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    
    String prefix = StringUtils.getServiceKey(map);
    if(methods ! =null && !methods.isEmpty()) {
        // Iterate over the MethodConfig list
        for (MethodConfig method : methods) {
            appendParameters(map, method, method.getName());
            String retryKey = method.getName() + ".retry";
            // Check if map contains methodname.retry
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                if ("false".equals(retryValue)) {
                    // Adds the retries configuration to methodname.retries
                    map.put(method.getName() + ".retries"."0"); }}// Add the "Properties" field in MethodConfig to attributes
            // Such as onreturn, onthrow, oninvoke, etc
            appendAttributes(attributes, method, prefix + "."+ method.getName()); checkAndConvertImplicitConfig(method, map, attributes); }}/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 5 ✨ ✨ rules -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

    // Get the service consumer IP address
    String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
    if (hostToRegistry == null || hostToRegistry.length() == 0) {
        hostToRegistry = NetUtils.getLocalHost();
    } else if (isInvalidLocalHost(hostToRegistry)) {
        throw new IllegalArgumentException("Specified invalid registry ip from property..." );
    }
    map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

    // Store attributes in the system context
    StaticContext.getSystemContext().putAll(attributes);

    // Create the proxy class
    ref = createProxy(map);

    // Construct the ConsumerModel according to the service name, ReferenceConfig, and the proxy class.
    // Store the ConsumerModel into the ApplicationModel
    ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
    ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}
Copy the code

The above code is long and does a lot of things. This is where the code is partitioned according to the code logic.

The first is the code between the start of the method and the splitter line 1. This code checks for the existence of the ConsumerConfig instance, creates a new instance if it doesn’t, and populates the ConsumerConfig fields either through system variables or through the Dubo.properties configuration file. Next, detect the generalized configuration and set the value of the interfaceClass based on the configuration. Now let’s look at the logic between secant 1 and secant 2. This logic is used to load the configuration corresponding to the interface name from system properties or configuration files and assign the result of the parsing to the URL field. The URL field is typically used for point-to-point calls. Moving on, the code between splitter lines 2 and splitter line 3 is used to detect if several core configuration classes are empty, and if they are, try to fetch from other configuration classes. The code between splitter 3 and splitter 4 is mainly used to collect various configurations and store them in a map. The code between split lines 4 and 5 is used to handle the MethodConfig instance. This instance contains event notification configurations such as onReturn, onThrow, onInvoke, and so on. The code from line 5 to the end of the method is mainly used to resolve the service consumer IP and create the proxy object by calling createProxy.

3.2 Reference Service

In this section we’ll start with createProxy. Literally, createProxy seems to be used only to createProxy objects. But that’s not the case; the method also calls other methods to build and merge Invoker instances. The details are as follows.

private T createProxy(Map<String, String> map) {
    URL tmpUrl = new URL("temp"."localhost".0, map);
    final boolean isJvmRefer;
    if (isInjvm() == null) {
        // if the url configuration is specified, no local reference is made
        if(url ! =null && url.length() > 0) {
            isJvmRefer = false;
        // Check whether local references are required according to the protocol, scope, and injVM parameters of the URL
        For example, if scope=local is explicitly specified, isInjvmRefer returns true
        } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
            isJvmRefer = true;
        } else {
            isJvmRefer = false; }}else {
        // Get injVM configuration value
        isJvmRefer = isInjvm().booleanValue();
    }

    // Local reference
    if (isJvmRefer) {
        // Generate a local reference URL with the protocol injvm
        URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
        // Call the refer method to build InjvmInvoker instance
        invoker = refprotocol.refer(interfaceClass, url);
        
    // Remote reference
    } else {
        // The URL is not empty, indicating that the user may want to make a point-to-point call
        if(url ! =null && url.length() > 0) {
            // If multiple urls need to be configured, use semicolons to split them
            String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
            if(us ! =null && us.length > 0) {
                for (String u : us) {
                    URL url = URL.valueOf(u);
                    if (url.getPath() == null || url.getPath().length() == 0) {
                        // Set the fully qualified name of the interface to url path
                        url = url.setPath(interfaceName);
                    }
                    
                    // Check whether the URL protocol is Registry, if the user wants to use the specified registry
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        // Convert map to a query string and add it to the URL as the value of the refer parameter
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    } else {
                        // Merge urls to remove some of the service provider's configurations from user-configured URL attributes,
                        For example, thread pool configuration. And retain some of the configuration of the service provider, such as version, group, timestamp, and so on
                        // Finally set the merged configuration to the URL query string.urls.add(ClusterUtils.mergeUrl(url, map)); }}}}else {
            // Load the registry URL
            List<URL> us = loadRegistries(false);
            if(us ! =null && !us.isEmpty()) {
                for (URL u : us) {
                    URL monitorUrl = loadMonitor(u);
                    if(monitorUrl ! =null) {
                        map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                    }
                    // Add the refer parameter to the URL and add the URL to the urlsurls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))); }}// No registry is configured, an exception is thrown
            if (urls.isEmpty()) {
                throw new IllegalStateException("No such any registry to reference..."); }}// Single registry or service provider (services directly connected, same below)
        if (urls.size() == 1) {
            // Build the Invoker instance by calling the refer of RegistryProtocol
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
            
        // Multiple registries or multiple service providers, or a mixture of both
        } else{ List<Invoker<? >> invokers =newArrayList<Invoker<? > > (); URL registryURL =null;

            // Get all invokers
            for (URL url : urls) {
                // Build Invoker by calling refer with refProtocol at run time
                // Load the specified Protocol instance according to the URL Protocol header and call the refer method of the instance
                invokers.add(refprotocol.refer(interfaceClass, url));
                if(Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { registryURL = url; }}if(registryURL ! =null) {
                AvailableCluster is used if the registry link is not empty
                URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                // Create an instance of StaticDirectory and merge invokers by Cluster
                invoker = cluster.join(new StaticDirectory(u, invokers));
            } else {
                invoker = cluster.join(new StaticDirectory(invokers));
            }
        }
    }

    Boolean c = check;
    if (c == null&& consumer ! =null) {
        c = consumer.isCheck();
    }
    if (c == null) {
        c = true;
    }
    
    // Invoker availability check
    if(c && ! invoker.isAvailable()) {throw new IllegalStateException("No provider available for the service...");
    }

    // Generate the proxy class
    return (T) proxyFactory.getProxy(invoker);
}
Copy the code

The above code is a lot, but the logic is clear. First check whether the call is local based on the configuration. If so, call the refer method of InjvmProtocol to generate an instance of InjvmInvoker. If not, it reads the directly connected configuration item, or the registry URL, and stores the read URL in urls. Then follow up based on the number of urls elements. If the number of urls elements is 1, the Invoker instance interface is built directly from the Protocol adaptive extension class. If the number of urls elements is greater than 1, that is, there are multiple registry or service directly linked urls, then the Invoker is built from the URL first. It then merges multiple invokers through the Cluster and finally calls ProxyFactory to generate the proxy class. The Invoker build process and the proxy class process are important, so these two processes will be examined in the next two sections.

3.2.1 create Invoker

Invoker is the core model of Dubbo and represents an executable. On the service provider side, Invoker is used to invoke the service provider class. On the service consumer side, Invoker is used to make remote calls. Invoker is built from the Protocol implementation class. Protocol implementation classes there are many, this section will analyze the most common two, respectively is the RegistryProtocol and DubboProtocol, the rest of the analysis by yourself. The following to analyze DubboProtocol refer method source code. As follows:

public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
    optimizeSerialization(url);
    / / create DubboInvoker
    DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
    invokers.add(invoker);
    return invoker;
}
Copy the code

The above method seems simple, but there is one call that needs our attention: getClients. This method is used to get a client instance of the type ExchangeClient. ExchangeClient does not actually have the ability to communicate; it needs to communicate based on a lower-level client instance. For example, NettyClient, MinaClient, etc. By default, Dubbo uses NettyClient to communicate. Next, let’s take a quick look at the logic of the getClients method.

private ExchangeClient[] getClients(URL url) {
    // Whether to share the connection
    boolean service_share_connect = false;
  	// Get the number of connections. The default value is 0, indicating that the connection is not configured
    int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
    // If connections is not configured, the connection is shared
    if (connections == 0) {
        service_share_connect = true;
        connections = 1;
    }

    ExchangeClient[] clients = new ExchangeClient[connections];
    for (int i = 0; i < clients.length; i++) {
        if (service_share_connect) {
            // Obtain the shared client
            clients[i] = getSharedClient(url);
        } else {
            // Initializes the new clientclients[i] = initClient(url); }}return clients;
}
Copy the code

Depending on the number of connections, the decision is to get a shared client or create a new client instance. By default, the shared client instance is used. The getSharedClient method also calls the initClient method, so let’s look at both methods together.

private ExchangeClient getSharedClient(URL url) {
    String key = url.getAddress();
    // Get ExchangeClient with reference counting
    ReferenceCountExchangeClient client = referenceClientMap.get(key);
    if(client ! =null) {
        if(! client.isClosed()) {// Increase the reference count
            client.incrementAndGetCount();
            return client;
        } else {
            referenceClientMap.remove(key);
        }
    }

    locks.putIfAbsent(key, new Object());
    synchronized (locks.get(key)) {
        if (referenceClientMap.containsKey(key)) {
            return referenceClientMap.get(key);
        }

        // Create ExchangeClient client
        ExchangeClient exchangeClient = initClient(url);
        / / will ExchangeClient instance to ReferenceCountExchangeClient, here use the decorator pattern
        client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
        referenceClientMap.put(key, client);
        ghostClientMap.remove(key);
        locks.remove(key);
        returnclient; }}Copy the code

The above method accesses the cache first. If the cache fails, a new ExchangeClient instance is created through initClient. And will the instance to ReferenceCountExchangeClient constructor to create a ExchangeClient instance with reference counting function. ReferenceCountExchangeClient internal implementation is simple, is not analyzed. Let’s look at the code for the initClient method again.

private ExchangeClient initClient(URL url) {

    // Get the client type. The default is netty
    String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));

    // Add codec and heartbeat packet parameters to the URL
    url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));

    // Check if the client type exists and throw an exception if it does not
    if(str ! =null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
        throw new RpcException("Unsupported client type: ...");
    }

    ExchangeClient client;
    try {
        // Gets the lazy configuration and determines the type of client to be created based on the configuration value
        if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
            // Create a lazy loaded ExchangeClient instance
            client = new LazyConnectExchangeClient(url, requestHandler);
        } else {
            // Create a normal ExchangeClient instanceclient = Exchangers.connect(url, requestHandler); }}catch (RemotingException e) {
        throw new RpcException("Fail to create remoting client for service...");
    }
    return client;
}
Copy the code

The initClient method first gets the client type configured by the user, which is netty by default. It then checks whether the client type configured by the user exists and throws an exception if it does not. Finally, the lazy configuration determines what type of client to create. Here LazyConnectExchangeClient code is not very complex, the class will be in the request method is invoked by the connect method of Exchangers create ExchangeClient client, this section is not analyzed the class code. Let’s examine Exchangers’ CONNECT method.

public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handler == null) {
        throw new IllegalArgumentException("handler == null");
    }
    url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");
    // Recovery instance, the default is HeaderExchangeClient
    return getExchanger(url).connect(url, handler);
}
Copy the code

As shown above, getExchanger loads an instance of HeaderExchange lient via SPI. This method is relatively simple, so take a look for yourself. Next, the implementation of HeaderExchange Lient is analyzed.

public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    // This contains multiple calls, as follows:
    // 1. Create HeaderExchangeHandler object
    // 2. Create DecodeHandler object
    // 3. Use Transporters to build a Client instance
    // 4. Create HeaderExchangeClient object
    return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}
Copy the code

There are more calls here, so let’s focus on the Transporters Connect method. As follows:

public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    ChannelHandler handler;
    if (handlers == null || handlers.length == 0) {
        handler = new ChannelHandlerAdapter();
    } else if (handlers.length == 1) {
        handler = handlers[0];
    } else {
        // If the number of handlers is greater than 1, create a ChannelHandler distributor
        handler = new ChannelHandlerDispatcher(handlers);
    }
    
    // Get the Transporter adaptive extension class and call connect to generate a Client instance
    return getTransporter().connect(url, handler);
}
Copy the code

As above, the getTransporter method returns an adaptive extension class that loads the specified Transporter implementation class at run time based on the client type. If the client type is not configured, the NettyTransporter is loaded by default and its connect method is invoked. As follows:

public Client connect(URL url, ChannelHandler listener) throws RemotingException {
    // Create NettyClient object
    return new NettyClient(url, listener);
}
Copy the code

Netty API to build a Netty client, you are interested in their own look. This concludes the analysis of the refer method for DubboProtocol. Next, analyze the logic of the refer method of RegistryProtocol.

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // Take the registry parameter value and set it to the protocol header
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    // Get the registry instance
    Registry registry = registryFactory.getRegistry(url);
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // Convert the URL query string to Map
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
    // Get the group configuration
    String group = qs.get(Constants.GROUP_KEY);
    if(group ! =null && group.length() > 0) {
        if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
                || "*".equals(group)) {
            // Load the MergeableCluster instance through SPI and call doRefer to continue the service reference logic
            returndoRefer(getMergeableCluster(), registry, type, url); }}// Call doRefer to continue the service reference logic
    return doRefer(cluster, registry, type, url);
}
Copy the code

The code above first sets the protocol header for the URL and then loads the registry instance based on the URL parameters. It then gets the group configuration, which determines the type of the first parameter of doRefer. The focus here is on the doRefer method, as follows:

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    // Create a RegistryDirectory instance
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    // Set up the registry and protocol
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    // Generate service consumer links
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);

    // Register service consumers, new node in the consumers directory
    if(! Constants.ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(Constants.REGISTER_KEY,true)) {
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }

    // Subscribe to node data such as Providers, Configurators, and Routers
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));

    // A registry can have multiple service providers, so you need to consolidate multiple service providers into one
    Invoker invoker = cluster.join(directory);
    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
    return invoker;
}
Copy the code

As above, the doRefer method creates an instance of RegistryDirectory, then generates a service consumer link and registers it with the registry. After the registration is complete, you can subscribe to the data of providers, Configurators, and Routers. After the subscription is complete, RegistryDirectory receives information about the children under these nodes. Since a service can be deployed on multiple servers, there will be multiple nodes in providers, which requires the Cluster to merge the multiple service nodes into one and generate an Invoker. About RegistryDirectory and Cluster.

3.2.2 Creating an Agent

Once Invoker is created, the next thing to do is generate proxy objects for the service interface. With a proxy object, you can make remote calls. The entry method generated by the proxy object is getProxy of ProxyFactory, which is analyzed next.

public <T> T getProxy(Invoker<T> invoker) throws RpcException {
    // Call overloaded methods
    return getProxy(invoker, false);
}

public <T> T getProxy(Invoker<T> invoker, boolean generic) throws RpcException { Class<? >[] interfaces =null;
    // Get the list of interfaces
    String config = invoker.getUrl().getParameter("interfaces");
    if(config ! =null && config.length() > 0) {
        // List of shard interfaces
        String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);
        if(types ! =null && types.length > 0) {
            interfaces = newClass<? >[types.length +2];
            // Set the service interface class and echoService. class to interfaces
            interfaces[0] = invoker.getInterface();
            interfaces[1] = EchoService.class;
            for (int i = 0; i < types.length; i++) {
                // Load the interface class
                interfaces[i + 1] = ReflectUtils.forName(types[i]); }}}if (interfaces == null) {
        interfaces = newClass<? >[]{invoker.getInterface(), EchoService.class}; }// Provide generalized call support for HTTP and hessian protocols, see Pull Request #1827
    if(! invoker.getInterface().equals(GenericService.class) && generic) {intlen = interfaces.length; Class<? >[] temp = interfaces;// Create a new interfaces array
        interfaces = newClass<? >[len +1];
        System.arraycopy(temp, 0, interfaces, 0, len);
        // Set GenericService.class to array
        interfaces[len] = GenericService.class;
    }

    // Call overloaded methods
    return getProxy(invoker, interfaces);
}

public abstract <T> T getProxy(Invoker
       
         invoker, Class
        [] types)
       ;
Copy the code

As shown above, the entire code is used to retrieve an array of interfaces, so let’s move on. getProxy(Invoker, Class<? >[]) this method is abstract. Let’s look at the implementation code of JavassistProxyFactory.

public <T> T getProxy(Invoker
       
         invoker, Class
        [] interfaces)
        {
    // Subclass Proxy (Proxy is an abstract class). Create a Proxy instance by calling the newInstance method of the Proxy subclass
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
Copy the code

The above code is not very much, the first is to get the Proxy subclass through Proxy getProxy method, then create the InvokerInvocationHandler object, and pass this object to newInstance to generate a Proxy instance. InvokerInvocationHandler Implements the JDK’s InvocationHandler interface, which is used to intercept interface class calls. The logic of this class is relatively simple and will not be analyzed here. Let’s focus on Proxy’s getProxy method, as follows.

public static Proxy getProxy(Class
       ... ics) {
    // Call overloaded methods
    return getProxy(ClassHelper.getClassLoader(Proxy.class), ics);
}

public static Proxy getProxy(ClassLoader cl, Class
       ... ics) {
    if (ics.length > 65535)
        throw new IllegalArgumentException("interface limit exceeded");

    StringBuilder sb = new StringBuilder();
    // Iterate over the interface list
    for (int i = 0; i < ics.length; i++) {
        String itf = ics[i].getName();
        // Check whether the type is interface
        if(! ics[i].isInterface())throw new RuntimeException(itf + " is not a interface."); Class<? > tmp =null;
        try {
            // Reload the interface class
            tmp = Class.forName(itf, false, cl);
        } catch (ClassNotFoundException e) {
        }

        // Check whether the interfaces are the same, TMP may be empty
        if(tmp ! = ics[i])throw new IllegalArgumentException(ics[i] + " is not visible from class loader");

        // The fully qualified name of the splicing interface is delimited by;
        sb.append(itf).append('; ');
    }

    // Use the concatenated interface name as the key
    String key = sb.toString();

    Map<String, Object> cache;
    synchronized (ProxyCacheMap) {
        cache = ProxyCacheMap.get(cl);
        if (cache == null) {
            cache = new HashMap<String, Object>();
            ProxyCacheMap.put(cl, cache);
        }
    }

    Proxy proxy = null;
    synchronized (cache) {
        do {
            // Get the Reference
      
        instance from the cache
      
            Object value = cache.get(key);
            if (value instanceofReference<? >) { proxy = (Proxy) ((Reference<? >) value).get();if(proxy ! =null) {
                    returnproxy; }}// Concurrency control ensures that only one thread can perform subsequent operations
            if (value == PendingGenerationMarker) {
                try {
                    // Other threads wait here
                    cache.wait();
                } catch (InterruptedException e) {
                }
            } else {
                // Place the flag bit in the cache and break out of the while loop for subsequent operations
                cache.put(key, PendingGenerationMarker);
                break; }}while (true);
    }

    long id = PROXY_CLASS_COUNTER.getAndIncrement();
    String pkg = null;
    ClassGenerator ccp = null, ccm = null;
    try {
        // Create a ClassGenerator object
        ccp = ClassGenerator.newInstance(cl);

        Set<String> worked = new HashSet<String>();
        List<Method> methods = new ArrayList<Method>();

        for (int i = 0; i < ics.length; i++) {
            // Check whether the interface access level is protected or private
            if(! Modifier.isPublic(ics[i].getModifiers())) {// Get the interface package name
                String npkg = ics[i].getPackage().getName();
                if (pkg == null) {
                    pkg = npkg;
                } else {
                    if(! pkg.equals(npkg))// Non-public interfaces must be in the same package, otherwise an exception will be thrown
                        throw new IllegalArgumentException("non-public interfaces from different packages"); }}// Add the interface to the ClassGenerator
            ccp.addInterface(ics[i]);

            // Iterate over the interface method
            for (Method method : ics[i].getMethods()) {
                // Get the description of the method
                String desc = ReflectUtils.getDesc(method);
                // If the method description string is already working, it is ignored. So if you think about this situation,
                // Interfaces A and B contain exactly the same method
                if (worked.contains(desc))
                    continue;
                worked.add(desc);

                int ix = methods.size();
                // Get the method return value typeClass<? > rt = method.getReturnType();// Get the parameter listClass<? >[] pts = method.getParameterTypes();[] args = new Object[1...N]
                StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
                for (int j = 0; j < pts.length; j++)
                    // Generate args[1...N] = ($w)$1... N;
                    code.append(" args[").append(j).append("] = ($w)$").append(j + 1).append(";");
                // Generate the invoker method call statement for the InvokerHandler interface, as follows:
                // Object ret = handler.invoke(this, methods[1...N], args);
                code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");

                // Return void
                if(! Void.TYPE.equals(rt))// Generate a return statement like return (java.lang.string) ret;
                    code.append(" return ").append(asArgument(rt, "ret")).append(";");

                methods.add(method);
                // Add method names, access control characters, parameter lists, method codes, and other information to ClassGeneratorccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString()); }}if (pkg == null)
            pkg = PACKAGE_NAME;

        // Build the interface proxy class name: PKG + ".proxy" + id, such as org.apache.dubo.proxy0
        String pcn = pkg + ".proxy" + id;
        ccp.setClassName(pcn);
        ccp.addField("public static java.lang.reflect.Method[] methods;");
        / / private generated Java. Lang. Reflect. InvocationHandler handler.
        ccp.addField("private " + InvocationHandler.class.getName() + " handler;");

        // Add a constructor to the interface proxy class with the InvocationHandler argument, such as:
        // porxy0(java.lang.reflect.InvocationHandler arg0) {
        // handler=$1;
    	// }
        ccp.addConstructor(Modifier.PUBLIC, newClass<? >[]{InvocationHandler.class},newClass<? > [0]."handler=$1;");
        // Add a default constructor for the interface proxy class
        ccp.addDefaultConstructor();
        
        // Generate the interface proxy classClass<? > clazz = ccp.toClass(); clazz.getField("methods").set(null, methods.toArray(new Method[0]));

        // Build a Proxy subclass name, such as Proxy1, Proxy2, etc
        String fcn = Proxy.class.getName() + id;
        ccm = ClassGenerator.newInstance(cl);
        ccm.setClassName(fcn);
        ccm.addDefaultConstructor();
        ccm.setSuperClass(Proxy.class);
        // Generate an implementation code for newInstance, an abstract Proxy method, like this:
        // public Object newInstance(java.lang.reflect.InvocationHandler h) { 
        // return new org.apache.dubbo.proxy0($1);
        // }
        ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
        // Generate the Proxy implementation classClass<? > pc = ccm.toClass();// Create a Proxy instance through reflection
        proxy = (Proxy) pc.newInstance();
    } catch (RuntimeException e) {
        throw e;
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage(), e);
    } finally {
        if(ccp ! =null)
            // Release resources
            ccp.release();
        if(ccm ! =null)
            ccm.release();
        synchronized (cache) {
            if (proxy == null)
                cache.remove(key);
            else
                / / write cache
                cache.put(key, new WeakReference<Proxy>(proxy));
            // Wake up other waiting threadscache.notifyAll(); }}return proxy;
}
Copy the code

The code above is quite complex and we have written a lot of comments. When you read this code, you need to understand the purpose of CCP and CCM, otherwise you will get confused. CCP generates proxy classes for service interfaces. For example, if we have a DemoService interface, this interface proxy class is generated by CCP. CCM is used for org.apache.dubbo.com mon. The bytecode. Proxy abstract class generation subclasses, mainly is the abstract methods to realize the Proxy class. Below to org. Apache. Dubbo. Demo. DemoService this interface, for example, look at the code in the class of interface agent is roughly how (ignoring the EchoService interface).

package org.apache.dubbo.common.bytecode;

public class proxy0 implements org.apache.dubbo.demo.DemoService {

    public static java.lang.reflect.Method[] methods;

    private java.lang.reflect.InvocationHandler handler;

    public proxy0(a) {}public proxy0(java.lang.reflect.InvocationHandler arg0) {
        handler = $1;
    }

    public java.lang.String sayHello(java.lang.String arg0) {
        Object[] args = new Object[1];
        args[0] = ($w) $1;
        Object ret = handler.invoke(this, methods[0], args);
        return(java.lang.String) ret; }}Copy the code

Well, that’s the end of the proxy generation logic. The whole process is complicated, so you need to be patient.

4, summarize

In this article, the process of service reference is analyzed in detail, and some logic, such as Directory and Cluster, is not analyzed at present. The functions of these interfaces and implementation classes are independent and will be analyzed separately later. For the moment, we can think of these classes as black boxes, as long as we know what they are for. So much for the service reference process.


yourLike and followIs the continuing power of the Solomon_ Shogo shell structure.

Hot historical Articles

  • 🔥Serverless Microservices elegant shutdown practices

  • 🔥 this algorithm can not understand! How are the 9 images presented

  • 🔥SpringBoot Mastery – Custom Condition annotations (Series 1)

  • 🔥Java is how to take off the beautiful woman’s clothes

  • 🔥 High-performance gateway was originally designed this way

  • 🔥REST FUL look still don’t understand, you play me!

  • How much 🔥Serverless affects programmers

  • 🔥 How does distributed transaction XID connect all microservices in tandem

  • 🔥 Microservices Distributed Transaction TCC core implementation

  • 🔥 hundreds of millions of traffic site performance optimization methodology steps

  • 🔥 microservice Nacos implements proximity access through CMDB to improve performance

  • 🔥 Micro service architecture DNS service registration and discovery mechanism

  • . More and more