10. Introduction of Dubbo Services

Keywords: Java Dubbo Spring Attribute jvm

1. Introduction of Dubbo Service Introduction

The first two articles Export of Dubbo's Service Export 1 to Local,Export of Dubbo's Service Export 2 to Remote The process of service export is analyzed in detail. In this article, we will continue to analyze the Service Citation process. In Dubbo, we can refer to remote services in two ways. The first is to refer to services directly, and the second is to refer to services based on registries. Direct service connection is only suitable for debugging or testing service scenarios, not for online environment. Therefore, I will focus on the process of referencing services through registries. Obtaining service configuration from the registry is only one part of the service reference process. In addition, service consumers need to go through the steps of Invoker creation and proxy class creation. These steps will be analyzed in subsequent chapters.

2, Service Citation principle

There are two times when Dubbo service references occur. The first is when the Spring container invokes the afterPropertiesSet method of ReferenceBean, and the second is when the corresponding service of ReferenceBean is injected into other classes. The difference between the two referencing services is that the first is hungry and the second is lazy. By default, Dubbo uses a lazy reference service. If you need to use hungry Chinese style, you can configure the init attribute of < dubbo: reference > to turn it on. Let's start with the getObject method of ReferenceBean by analyzing the default configuration of Dubbo. When our services are injected into other classes, Spring calls the getObject method for the first time and executes the service reference logic by that method. According to the usual practice, the configuration inspection and collection work should be carried out before the specific work is carried out. Then according to the information collected, there are three ways to determine the service. The first is to refer to the local (JVM) service, the second is to refer to the remote service through direct connection, and the third is to refer to the remote service through the registry. 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, which need to be merged into one instance through the cluster management class Cluster. The merged Invoker instance already has the ability to call local or remote services, but it cannot be exposed to users, which can cause intrusion into user business code. At this point, the framework also needs to generate proxy classes for service interfaces through the ProxyFactory class, and let the proxy class call Invoker logic. It avoids the intrusion of Dubbo framework code into business code and makes the framework easier to use.
The above is the general principle of Service Citation. Let's go deep into the code and analyze the details of Service Citation in detail.

3. Source code analysis

3.1. Configuration Check and Collection

// ReferenceBean implements the InitializingBean interface, so this method is invoked when the Spring container is initialized, which corresponds to the first reference opportunity mentioned above.
// By default, the reference service method does not follow here, and you need to configure init=true
@Override
public void afterPropertiesSet() throws Exception {
    // Delete some code
    
    // There are two times for Dubbo service reference, the first is to call the afterProperties Set of Reference Bean in the Spring container
    // Method refers to services, and the second refers to services corresponding to ReferenceBean when they are injected into other classes. The timing of these two referencing services
    // The difference is that the first is hungry and the second is lazy. By default, Dubbo uses lazy reference services. If needed
    // Hungry Chinese style can be opened by configuring the init attribute of <dubbo:reference>.
    Boolean b = isInit();
    if (b == null && getConsumer() != null) {
        b = getConsumer().isInit();
    }
    if (b != null && b.booleanValue()) {
        getObject();
    }
}
/**
 * The whole analysis process starts with the getObject method of ReferenceBean. When our services are injected into other classes,
 * Spring The getObject method is called the first time, and the service reference logic is executed by this method.
 */
@Override
public Object getObject() throws Exception {
    return get();
}

public synchronized T get() {
    if (destroyed) {
        throw new IllegalStateException("Already destroyed!");
    }
    // If ref is empty or not, it is created by init method.
    if (ref == null) {
        // The init method is mainly used to handle configuration and to call createProxy to generate proxy classes.
        init();
    }
    return ref;
}

The init method is relatively long. In order to typeset, it is divided into several sections and deletes code such as exception capture and log record. The core code is ref = createProxy(map).

private void init() {
    // Avoid repeated initialization
    if (initialized) {
        return;
    }
    initialized = true;
    // Check interface legitimacy
    if (interfaceName == null || interfaceName.length() == 0) {
        throw new IllegalStateException("xxx");
    }
    // Get the global configuration of consumer
    checkDefault();
    appendProperties(this);
    if (getGeneric() == null && getConsumer() != null) {
        setGeneric(getConsumer().getGeneric());
    }
    // Detecting whether it is a generalization interface
    if (ProtocolUtils.isGeneric(getGeneric())) {
        interfaceClass = GenericService.class;
    } else {
        try {
            // Loading class
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        checkInterfaceAndMethods(interfaceClass, methods);
    }
    // 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) {
        // Getting parse file paths from system properties
        resolveFile = System.getProperty("dubbo.resolve.file");
        // Load the configuration file from the specified location
        if (resolveFile == null || resolveFile.length() == 0) {
            File userResolveFile = 
                new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
            if (userResolveFile.exists()) {
                // Get the absolute path of the file
                resolveFile = userResolveFile.getAbsolutePath();
            }
        }
        if (resolveFile != null && resolveFile.length() > 0) {
            Properties properties = new Properties();
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(new File(resolveFile));
                // Load configuration from file
                properties.load(fis);
            } 
            // Get the configuration corresponding to the interface name
            resolve = properties.getProperty(interfaceName);
        }
    }
    if (resolve != null && resolve.length() > 0) {
        // Assign resolve to url for point-to-point direct connection
        url = resolve;
    }
    if (consumer != null) {
        if (application == null) {
            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();
        }
    }
    
    // Detecting Application Legitimacy
    checkApplication();
    // Detecting the validity of local stub configuration
    checkStubAndMock(interfaceClass);

    // Adding side, protocol version information, timestamp and process number to map, side indicates which side it is on and is currently on the service consumer side.
    Map<String, String> map = new HashMap<String, String>();
    Map<Object, Object> attributes = new HashMap<Object, Object>();
    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-generalization services
    if (!isGeneric()) {
        // Get version
        String revision = Version.getVersion(interfaceClass, version);
        if (revision != null && revision.length() > 0) {
            map.put("revision", revision);
        }

        // Get a 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 field information of Application Config, ConsumerConfig, Reference Config and other objects to map
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, consumer, Constants.DEFAULT_KEY);
    appendParameters(map, this);
    // Like com.alibaba.dubbo.demo.DemoService
    String prefix = StringUtils.getServiceKey(map);
    if (methods != null && !methods.isEmpty()) {
        // Traversing 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)) {
                    // Configure methodName.retries by adding retries
                    map.put(method.getName() + ".retries", "0");
                }
            }
            // Add the Properties field in MethodConfig to attributes
            // For example, onreturn, onthrow, oninvoke, etc.
            appendAttributes(attributes, method, prefix + "." + method.getName());
            checkAndConvertImplicitConfig(method, map, attributes);
        }
    }
    // Get the service consumer's 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("");
    }
    map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

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

    // Create proxy classes, core
    ref = createProxy(map);

    // According to the service name, ReferenceConfig, the proxy class builds ConsumerModel.
    // And store ConsumerModel in Application Model
    ConsumerModel consumerModel = 
                new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
    ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}

3.2. Create Invoker and Agent

The core code of the above init() method is ref = createProxy(map), which is analyzed below.

3.2.1. Overall analysis

// Literally, createProxy seems to be used only to create proxy objects, but in fact it is not.
// This method also calls other methods to build and merge Invoker instances.
private T createProxy(Map<String, String> map) {
    URL tmpUrl = new URL("temp", "localhost", 0, map);
    final boolean isJvmRefer;
    if (isInjvm() == null) {
        // The url configuration is specified without local reference. I understand that the url is used for direct connection.
        if (url != null && url.length() > 0) {
            isJvmRefer = false;
        }
        // Check whether local references are needed according to the protocol of url, scope, injvm and other parameters
        // For example, if the user explicitly configures scope=local, then isInjvmRefer returns true
        else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
            isJvmRefer = true;
        } else {
            isJvmRefer = false;
        }
    } else {
        // Get the injvm configuration value
        isJvmRefer = isInjvm().booleanValue();
    }
    // Local reference
    if (isJvmRefer) {
        // Generate local reference URL s, protocol injvm
        URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, 
                                                   interfaceClass.getName()).addParameters(map);
        // Call 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 point-to-point calls
        if (url != null && url.length() > 0) {
            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) {
                        url = url.setPath(interfaceName);
                    }
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, 
                                                              StringUtils.toQueryString(map)));
                    } else {
                        urls.add(ClusterUtils.mergeUrl(url, map));
                    }
                }
            }
        }
        else {
            // Loading 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 urls
                    urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, 
                                                             StringUtils.toQueryString(map)));
                }
            }
            // Registry not configured, exception thrown
            if (urls.isEmpty()) {
                // Throwing anomaly
            }
        }
        // Single Registry or Service Provider (Direct Service Connection, the same below)
        if (urls.size() == 1) {
            // 1. Call Registry Protocol's refer to construct an Invoker instance, and generally follow this logic
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
        }
        // Multiple registries, multiple service providers, or a mix of both
        else {
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null; 
            for (URL url : urls) {
                // Get all Invokers and call refer to build Invoker through refprotocol, which runs
                // 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 the registry link is not empty, Available Cluster will be used
            if (registryURL != null) {
                URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                // Create a StaticDirectory instance and merge multiple Invoker s 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) {
        // default true
        c = true;
    }

    // invoker availability check
    if (c && !invoker.isAvailable()) {
        // Throwing anomaly
    }

    // 2. Generating proxy classes, core
    // After Invoker is created, the next thing to do is to generate proxy objects for service interfaces. With proxy objects,
    // Remote calls can be made. The entry method generated by proxy objects is ProxyFactory's getProxy.
    return (T) proxyFactory.getProxy(invoker);
}

3.2.2. Create Invoker

// Following is an analysis of creating Invoker, a single registry or service provider (direct service connection, the same below)
if (urls.size() == 1) {
    // 1. Call Registry Protocol's refer to construct an Invoker instance, and generally follow this logic
    invoker = refprotocol.refer(interfaceClass, urls.get(0));
}
  public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // protocol for zookeeper, debugging plus
    String protocol = url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY);
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, 
                               Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    // Get an instance of a registry
    Registry registry = registryFactory.getRegistry(url);
    // Here the type is ""
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }
    // Convert 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 Mergeable Cluster instance through SPI and call doRefer to continue executing service reference logic
            return doRefer(getMergeableCluster(), registry, type, url);
        }
    }
    // Call doRefer to continue executing the service reference logic
    return doRefer(cluster, registry, type, url);
}

3.2.3. Creating Agents

Posted by rushenas on Sun, 06 Oct 2019 12:42:49 -0700