Dubbo service exposure process

Objective: To analyze the service exposure process from a source code perspective.

preface

Originally this article is a content to write asynchronous transformation, but recently I have been thinking about how to write a part of the optimization transformation to make readers understand more. I think we need to start with the whole invocation chain, figure out when the function is happening, or more generally, when or in what scenario the code is being executed, then analyze how it is implemented internally, and finally explain the benefits of this transformation.

In the previous articles, I rarely mentioned the relationship between each invocation chain, and each module was not connected. In addition, to explain the asynchronous transformation, I think it is very necessary to understand the process of service exposure and reference first, so I will use two articles to explain the process of service exposure and service reference.

Service exposure process

The service exposure process can be roughly divided into three parts:

  1. Front work, mainly used to check parameters, URL assembly.
  2. Exporting services includes exposing services to local (JVM) and exposing services to remote.
  3. Register services with a registry for service discovery.

Exposure starting point

Spring has an ApplicationListener interface that defines an onApplicationEvent() method that is fired when any event occurs in the container.

The ServiceBean class in Dubbo implements this interface and implements the onApplicationEvent method:

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
  	// If the service is not exposed and the service is not unexposed, the log is printed
    if(! isExported() && ! isUnexported()) {if (logger.isInfoEnabled()) {
            logger.info("The service ready on spring started. service: " + getInterface());
        }
      	/ / exportexport(); }}Copy the code

The service is exposed as long as it is not exposed and the service has not been unexposed. Execute the export method. The following is related to the sequence diagram on the official website, comparing with the sequence diagram to see the following process.

I will add the scope of each step in the sequence diagram in the subheading below, for example, the preloading work described below is actually 1 in the sequence diagram :export(), then I will write 1 in the parentheses of the heading.

The eighth step of service exposure is gone.

Pre-work (1)

The pre-work mainly consists of two parts, namely configuration check and URL assembly. Before exposing the service, Dubbo needs to check that the user’s configuration is correct, or supplement the default configuration for the user. Now that the configuration check is complete, you need to assemble urls from these configurations. In Dubbo, urls are important. Dubbo uses the URL as the configuration carrier, and all extension points get the configuration from the URL.

Check the configuration

After the export method is called, the export method in ServiceConfig is executed.

public synchronized void export(a) {
    // Check and update the configuration
    checkAndUpdateSubConfigs();

    // If it should not be exposed, end it
    if(! shouldExport()) {return;
    }

    // If lazy loading is used, the service is exposed after delay time
    if (shouldDelay()) {
        delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS);
    } else {
        // Expose servicedoExport(); }}Copy the code

You can see that the first thing you do is check and update the configuration, using the checkAndUpdateSubConfigs method in ServiceConfig. It then checks if it should be exposed, if not, it ends directly, then checks if lazy loading is configured, if so, it uses timer to achieve the purpose of lazy loading.

checkAndUpdateSubConfigs()
public void checkAndUpdateSubConfigs(a) {
    // Use default configs defined explicitly on global configs
    // It is used to check whether core configuration objects such as provider and application are empty.
    // If empty, try to get the corresponding instance from another configuration class object.
    completeCompoundConfigs();
    // Config Center should always being started first.
    // Enable the configuration center
    startConfigCenter();
    // Check whether the provider is empty. If it is empty, create a new one and initialize it using system variables
    checkDefault();
    // Check if application is empty
    checkApplication();
    // Check if the registry is empty
    checkRegistry();
    // Check whether protocols is null
    checkProtocol();
    this.refresh();
    // Check whether the metadata center configuration is empty
    checkMetadataReport();

    // The service interface name cannot be empty, otherwise an exception will be thrown
    if (StringUtils.isEmpty(interfaceName)) {
        throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
    }

    // Check if ref is a generalized service type
    if (ref instanceof GenericService) {
        // Set interfaceClass to GenericService
        interfaceClass = GenericService.class;
        if (StringUtils.isEmpty(generic)) {
            // Set generic = truegeneric = Boolean.TRUE.toString(); }}else {
        try {
            // Get the interface type
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        // Check the necessary fields in the interfaceClass and 
      
        tags
      
        checkInterfaceAndMethods(interfaceClass, methods);
        // Check the validity of ref
        checkRef();
        generic = Boolean.FALSE.toString();
    }
    // Stub local is configured as a local stub
    if(local ! =null) {
        if ("true".equals(local)) {
            local = interfaceName + "Local"; } Class<? > localClass;try {
            localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        if(! interfaceClass.isAssignableFrom(localClass)) {throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface "+ interfaceName); }}if(stub ! =null) {
        if ("true".equals(stub)) {
            stub = interfaceName + "Stub"; } Class<? > stubClass;try {
            stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
        if(! interfaceClass.isAssignableFrom(stubClass)) {throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface "+ interfaceName); }}// Verify the validity of the local stub
    checkStubAndLocal(interfaceClass);
    // Mock validity check
    checkMock(interfaceClass);
}
Copy the code

As you can see, this method validates various configurations and updates some configurations. I won’t go into the details of the inspection because the whole process of service exposure is the focus of this article.

After the shouldExport() and shouldDelay() methods, the doExport() method of ServiceConfig is executed

doExport()
protected synchronized void doExport(a) {
    // The unexported value is true if an unexposed method is called
    if (unexported) {
        throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
    }
    // If the service is already exposed, end it
    if (exported) {
        return;
    }
    // The Settings are exposed
    exported = true;

    // If path is empty, the interface name is assigned
    if (StringUtils.isEmpty(path)) {
        path = interfaceName;
    }
    // Multi-protocol multi-registry exposure service
    doExportUrls();
}
Copy the code

DoExportUrls () method for ServiceConfig is used to evaluate whether a service is exposed in one audit, and then doExportUrls() method for ServiceConfig is executed to support multi-protocol multi-registry exposure services.

doExportUrls()
private void doExportUrls(a) {
    // Load the registry link
    List<URL> registryURLs = loadRegistries(true);
    // Traverse protocols and expose the service under each protocol
    for (ProtocolConfig protocolConfig : protocols) {
        // Use path, group, or version as the unique service key
        String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
        ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
        ApplicationModel.initProviderModel(pathKey, providerModel);
        Assembly / / URLdoExportUrlsFor1Protocol(protocolConfig, registryURLs); }}Copy the code

It can be seen from the method:

  • The loadRegistries() method loads the registry link.
  • The uniqueness of the service is determined by path, group, and version.
  • The doExportUrlsFor1Protocol() method starts the URL assembly.
loadRegistries()
protected List<URL> loadRegistries(boolean provider) {
    // check && override if necessary
    List<URL> registryList = new ArrayList<URL>();
    // If registries is empty, return the empty collection directly
    if (CollectionUtils.isNotEmpty(registries)) {
        // Walk through the registry configuration collection registries
        for (RegistryConfig config : registries) {
            // Get the address
            String address = config.getAddress();
            // If the address is empty, set it to 0.0.0.0
            if (StringUtils.isEmpty(address)) {
                address = Constants.ANYHOST_VALUE;
            }
            // If the address is N/A, skip it
            if(! RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) { Map<String, String> map =new HashMap<String, String>();
                // Add field information from ApplicationConfig to map
                appendParameters(map, application);
                // Add the RegistryConfig field information to the map
                appendParameters(map, config);
                / / add the path
                map.put(Constants.PATH_KEY, RegistryService.class.getName());
                // Add the protocol version, release version, and timestamp to the map
                appendRuntimeParameters(map);
                // If there is no protocol in map, dubbo is used by default
                if(! map.containsKey(Constants.PROTOCOL_KEY)) { map.put(Constants.PROTOCOL_KEY, Constants.DUBBO_PROTOCOL); }Address may contain more than one registry IP, so the result is a list of urls
                List<URL> urls = UrlUtils.parseURLs(address, map);

                // Iterate over the list of urls
                for (URL url : urls) {
                    // Set the URL protocol header to registry
                    url = URLBuilder.from(url)
                            .addParameter(Constants.REGISTRY_KEY, url.getProtocol())
                            .setProtocol(Constants.REGISTRY_PROTOCOL)
                            .build();
                    // Decide whether to add the URL to the registryList using the following criteria:
                    // If it is a service provider, it is a registry service or a consumer side, and it is a subscription service
                    // Then add to the registryList
                    if ((provider && url.getParameter(Constants.REGISTER_KEY, true) | | (! provider && url.getParameter(Constants.SUBSCRIBE_KEY,true))) {
                        registryList.add(url);
                    }
                }
            }
        }
    }
    return registryList;
}
Copy the code

Assemble the URL

As I mentioned in a previous article, Dubbo uses urls internally to carry configurations throughout the call chain, which is the carrier of configuration. This is where the configuration of the service is assembled into the URL. As mentioned above, doExportUrlsFor1Protocol() method of ServiceConfig is executed to audit each protocol configuration and expose the service in each protocol. The first half of this method implements the URL assembly logic. The second half implements logic such as exposing the Dubbo service, which is separated by dividing lines.

doExportUrlsFor1Protocol()
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    // Get the protocol name
    String name = protocolConfig.getName();
    // If empty, dubbo is the default
    if (StringUtils.isEmpty(name)) {
        name = Constants.DUBBO;
    }

    Map<String, String> map = new HashMap<String, String>();
    // Set the service provider volume
    map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);

    // Add the protocol version, release version, and timestamp to the map
    appendRuntimeParameters(map);
    // Add metrics, application, Module, provider, protocol to map
    appendParameters(map, metrics);
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, provider, Constants.DEFAULT_KEY);
    appendParameters(map, protocolConfig);
    appendParameters(map, this);
    // If method's configuration list is not empty
    if (CollectionUtils.isNotEmpty(methods)) {
        // Iterate over the method configuration list
        for (MethodConfig method : methods) {
            // Add the method name to map
            appendParameters(map, method, method.getName());
            // Add the field information of the MethodConfig object to the map, key = method name. The property name.
            
      
            // key = sayHello. Retries, map = {"sayHello. Retries ": 2, "XXX ": "yyy"}
            String retryKey = method.getName() + ".retry";
            if (map.containsKey(retryKey)) {
                String retryValue = map.remove(retryKey);
                // If retryValue is false, the system does not retry and sets the value to 0
                if ("false".equals(retryValue)) {
                    map.put(method.getName() + ".retries"."0"); }}// Get the ArgumentConfig list
            List<ArgumentConfig> arguments = method.getArguments();
            if (CollectionUtils.isNotEmpty(arguments)) {
                // Traverse the ArgumentConfig list
                for (ArgumentConfig argument : arguments) {
                    // convert argument type
                    // // checks whether the type attribute is empty or an empty string
                    if(argument.getType() ! =null && argument.getType().length() > 0) {
                        // Use reflection to get a collection of all methods for the service
                        Method[] methods = interfaceClass.getMethods();
                        // visit all methods
                        if(methods ! =null && methods.length > 0) {
                            // Iterate over all methods
                            for (int i = 0; i < methods.length; i++) {
                                // Get the method name
                                String methodName = methods[i].getName();
                                // target the method, and get its signature
                                // Find the target method
                                if (methodName.equals(method.getName())) {
                                    // Get the target method's argument type array argtypes by reflectionClass<? >[] argtypes = methods[i].getParameterTypes();// one callback in the method
                                    // if subscript is -1
                                    if(argument.getIndex() ! = -1) {
                                        // Check whether the name of argType matches the Type attribute in ArgumentConfig
                                        if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                            // Add ArgumentConfig field information to the map
                                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                        } else {
                                            // If not, an exception is thrown
                                            throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:"+ argument.getType()); }}else {
                                        // multiple callbacks in the method
                                        // Iterate over the argument types array argtypes to find arguments of type argument.type
                                        for (int j = 0; j < argtypes.length; j++) { Class<? > argclazz = argtypes[j];if (argclazz.getName().equals(argument.getType())) {
                                                // If found, add ArgumentConfig field information to the map
                                                appendParameters(map, argument, method.getName() + "." + j);
                                                if(argument.getIndex() ! = -1&& argument.getIndex() ! = j) {throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } else if(argument.getIndex() ! = -1) {
                        // The user does not configure the type attribute, but the index attribute is configured, and index! = -1, it is directly added to the map
                        appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                    } else {
                        // Throw an exception
                        throw new IllegalArgumentException("Argument config must set index or type attribute.eg: 
       or 
      
       "
      ); }}}}// end of methods for
    }

    // If it is a generalization call, set generic and methods in map
    if (ProtocolUtils.isGeneric(generic)) {
        map.put(Constants.GENERIC_KEY, generic);
        map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
    } else {
        // Get the version number
        String revision = Version.getVersion(interfaceClass, version);
        / / in the map
        if(revision ! =null && revision.length() > 0) {
            map.put(Constants.REVISION_KEY, revision);
        }

        // Get the set of methods
        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
        // If no, an alarm is generated
        if (methods.length == 0) {
            logger.warn("No method found in service interface " + interfaceClass.getName());
            // Set method to *
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            // Otherwise join the method set
            map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ",")); }}// Add the token value to map
    if(! ConfigUtils.isEmpty(token)) {if (ConfigUtils.isDefault(token)) {
            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
        } else{ map.put(Constants.TOKEN_KEY, token); }}// export service
    // Get the address
    String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
    // Get the port number
    Integer port = this.findConfigedPorts(protocolConfig, name, map);
    / / generated & emsp; URL
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

    / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -


    // Load the ConfiguratorFactory and generate a Configurator instance to determine if an implementation of the protocol exists
    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
        // Configure the URL by instance
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }

    String scope = url.getParameter(Constants.SCOPE_KEY);
    // don't export when none is configured
    // // If scope = None, do nothing
    if(! Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {// export to local if the config is not remote (export to remote only when config is remote)
        // // scope ! = remote, local exposure
        if(! Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) {// Local exposure
            exportLocal(url);
        }
        // export to remote if the config is not local (export to local only when config is local)
        // // scope ! = local, export to remote
        if(! Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {if (logger.isInfoEnabled()) {
                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
            }
            // If the registry link set is not empty
            if (CollectionUtils.isNotEmpty(registryURLs)) {
                // Walk through the registry
                for (URL registryURL : registryURLs) {
                    // Add dynamic configuration
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                    // Load the monitor link
                    URL monitorUrl = loadMonitor(registryURL);
                    if(monitorUrl ! =null) {
                        // Add the monitor configuration
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }
                    if (logger.isInfoEnabled()) {
                        logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                    }

                    // For providers, this is used to enable custom proxy to generate invoker
                    // Get the proxy mode
                    String proxy = url.getParameter(Constants.PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        // Add proxy mode to registry to URL
                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                    }

                    // Generate Invoker for the service provider class (ref)Invoker<? > invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));/ / DelegateProviderMetaDataInvoker used to hold the Invoker and ServiceConfig
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    // Expose the service and generate an ExporterExporter<? > exporter = protocol.export(wrapperInvoker);// Add to the exposed collectionexporters.add(exporter); }}else {
                // If there is no registry, only the service is exposedInvoker<? > invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); DelegateProviderMetaDataInvoker wrapperInvoker =new DelegateProviderMetaDataInvoker(invoker, this); Exporter<? > exporter = protocol.export(wrapperInvoker); exporters.add(exporter); }/ * * *@since 2.7.0
             * ServiceData Store
             */
            MetadataReportService metadataReportService = null;
            // If the metadata center service is not empty, publish the service, that is, record the partial configuration in the URL in the metadata center
            if((metadataReportService = getMetadataReportService()) ! =null) { metadataReportService.publishProvider(url); }}}this.urls.add(url);
}
Copy the code

First look at the upper part of the partition line, is the whole process of URL assembly, I think it can be roughly divided into the following steps:

  1. It puts metrics, Application, Module, provider, Protocol, and so on into the map.
  2. For methods, check whether the service has the configured method and whether the parameter exists in the method signature before adding the method configuration to the map.
  3. Add generalization calls, version numbers, method or Methods, and tokens to the map
  4. Get the service exposure address and port number and assemble the URL from the map data.

Create invoker (2,3)

See doExportUrlsFor1Protocol() method for the lower half of the source code exposed remotely. When the exposure is generated, the service is exposed, and the process inside the exposure is then analyzed in detail. You can see that invokers are created through the broker factory regardless of whether they are exposed locally or remotely. This brings us to the ProxyFactory of the sequence diagram above. Let’s start by looking at how Invoker was born. Invoker is created by ProxyFactory. Dubbo’s default ProxyFactory implementation class is JavassistProxyFactory. There is a getInvoker() method in JavassistProxyFactory.

Get the Invoker method

getInvoker()
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
    // // creates a Wrapper for the target class
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
    // Create an anonymous Invoker class object and implement the doInvoke method.
    return new AbstractProxyInvoker<T>(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName, Class
       [] parameterTypes, Object[] arguments) throws Throwable {
            // Call the Wrapper's invokeMethod method, which eventually calls the target method
            returnwrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); }}; }Copy the code

As you can see, this method simply creates an anonymous Invoker class object and calls the Wrapper.invokemethod () method in the doInvoke() method. Wrapper is an abstract Class that can only be subclassed using the getWrapper(Class) method. During the creation of the Wrapper subclass, the subclass code generation logic parses the Class object passed in by the getWrapper method to retrieve information such as Class methods, Class member variables, and so on. As well as generating code for the invokeMethod method and some other method code. Once the code is generated, Javassist generates the Class object, and finally reflection creates the Wrapper instance. So let’s look at the getWrapper() method first:

getWrapper()
public static Wrapper getWrapper(Class
        c) {
    while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class.
    {
        // Returns the superclass of this object
        c = c.getSuperclass();
    }

    // If the superclass is Object, return the Wrapper subclass
    if (c == Object.class) {
        return OBJECT_WRAPPER;
    }

    // Get the Wrapper instance from the cache
    Wrapper ret = WRAPPER_MAP.get(c);
    // If there is no hit, create a Wrapper
    if (ret == null) {
        / / create a Wrapper
        ret = makeWrapper(c);
        // Write cache
        WRAPPER_MAP.put(c, ret);
    }
    return ret;
}
Copy the code

This method only caches the Wrapper. The main logic is in makeWrapper().

makeWrapper()
    // Check if c is a primitive type and throw an exception if it is
    if (c.isPrimitive()) {
        throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c);
    }

    // Get the class name
    String name = c.getName();
    // Get the classloader
    ClassLoader cl = ClassHelper.getClassLoader(c);

    // c1 stores the setPropertyValue method code
    StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ ");
    // c2 is used to store the getPropertyValue method code
    StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ ");
    C3 is used to store the invokeMethod method code
    StringBuilder c3 = new StringBuilder("public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws " + InvocationTargetException.class.getName() + "{");

    // Generate type conversion code and exception catching code, such as:
    // DemoService w; try { w = ((DemoServcie) $1); }}catch(Throwable e){ throw new IllegalArgumentException(e); }
    c1.append(name).append(" w; try{ w = ((").append(name).append($1 ")); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
    c2.append(name).append(" w; try{ w = ((").append(name).append($1 ")); }catch(Throwable e){ throw new IllegalArgumentException(e); }");
    c3.append(name).append(" w; try{ w = ((").append(name).append($1 ")); }catch(Throwable e){ throw new IllegalArgumentException(e); }");

    // PTS is used to store member variable names and typesMap<String, Class<? >> pts =new HashMap<>(); // <property name, property types>
    // ms is used to store Method description information and Method instances
    Map<String, Method> ms = new LinkedHashMap<>(); // <method desc, Method instance>
    // MNS is a list of method names
    List<String> mns = new ArrayList<>(); // method names.
    // DMNS is used to store the name of the method defined in the current class
    List<String> dmns = new ArrayList<>(); // declaring method names.

    // get all public field.
    // Get public access level fields and generate conditional statements for all fields
    for(Field f : c.getFields()) { String fn = f.getName(); Class<? > ft = f.getType();if (Modifier.isStatic(f.getModifiers()) || Modifier.isTransient(f.getModifiers())) {
            // Ignore variables with static or TRANSIENT keywords
            continue;
        }
        // Generate conditional judgment and assignment statements, such as:
        // if( $2.equals("name") ) { w.name = (java.lang.String) $3; return; }
        // if( $2.equals("age") ) { w.age = ((Number) $3).intValue(); return; }
        c1.append(" if( $2.equals(\"").append(fn).append("\") ){ w.").append(fn).append("=").append(arg(ft, "$3")).append("; return; }");
        // Generate conditional judgment and return statements, such as:
        // if( $2.equals("name") ) { return ($w)w.name; }
        c2.append(" if( $2.equals(\"").append(fn).append("\") ){ return ($w)w.").append(fn).append("; }");
        // Store < field name, field type > key-value pairs in PTS
        pts.put(fn, ft);
    }

    // Get all the methods of class C
    Method[] methods = c.getMethods();
    // get all public method.
    // Check if c contains methods declared in the current class
    boolean hasMethod = hasMethods(methods);
    // If yes
    if (hasMethod) {
        c3.append(" try{");
        for (Method m : methods) {
            //ignore Object's method.
            // Ignore methods defined in Object
            if (m.getDeclaringClass() == Object.class) {
                continue;
            }

            // Get the method name
            String mn = m.getName();
            // Generate a method name statement, such as:
            // if ( "sayHello".equals( $2 )
            c3.append(" if( \"").append(mn).append("\".equals( $2 ) ");
            int len = m.getParameterTypes().length;
            // Generate the "number of arguments passed at runtime versus the length of the method argument list" statement, for example:
            // && $3.length == 2
            c3.append("&").append(" $3.length == ").append(len);

            boolean override = false;
            for (Method m2 : methods) {
                // Check whether the method is overloaded if the method object is different && the method name is the same
                if(m ! = m2 && m.getName().equals(m2.getName())) { override =true;
                    break; }}// To handle overloaded methods, consider the following:
            // 1. void sayHello(Integer, String)
            // 2. void sayHello(Integer, Integer)
            // The method names are the same, and the parameter lists are the same length.
            // We need to further determine the method parameter type
            if (override) {
                if (len > 0) {
                    for (int l = 0; l < len; l++) {
                        c3.append("&").append(" $3[").append(l).append("].getName().equals(\"")
                                .append(m.getParameterTypes()[l].getName()).append("\")"); }}}// Add) {, complete the method judgment statement, at which point the generated code might look like this (formatted) :
            // if ("sayHello".equals($2)
            // && $3.length == 2
            // && $3[0].getName().equals("java.lang.Integer")
            // && $3[1].getName().equals("java.lang.String")) {
            c3.append("{");

            Generate the target method call statement based on the return value type
            if (m.getReturnType() == Void.TYPE) {
                // w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null;
                c3.append(" w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");").append(" return null;");
            } else {
                // return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);
                c3.append(" return ($w)w.").append(mn).append('(').append(args(m.getParameterTypes(), "$4")).append(");");
            }

            c3.append("}");

            // Add the method name to the MNS collection
            mns.add(mn);
            // Check whether the current method is declared in c
            if (m.getDeclaringClass() == c) {
                // If so, add the current method name to DMNS
                dmns.add(mn);
            }
            ms.put(ReflectUtils.getDesc(m), m);
        }
        // Add an exception capture statement
        c3.append(" } catch(Throwable e) { ");
        c3.append(" throw new java.lang.reflect.InvocationTargetException(e); ");
        c3.append("}");
    }

    // add NoSuchMethodException
    c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ". \ "); }");

    // deal with get/set method.
    Matcher matcher;
    // Handle get/set methods
    for (Map.Entry<String, Method> entry : ms.entrySet()) {
        // Get the method name
        String md = entry.getKey();
        // get Method
        Method method = entry.getValue();
        // If the method starts with get
        if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            // Get the attribute name
            String pn = propertyName(matcher.group(1));
            // Generate attribute judgments and return statements as shown in the following example:
            // if( $2.equals("name") ) { return ($w).w.getName(); }
            c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append((" "); }");
            pts.put(pn, method.getReturnType());
        } else if ((matcher = ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            String pn = propertyName(matcher.group(1));
            c2.append(" if( $2.equals(\"").append(pn).append("\") ){ return ($w)w.").append(method.getName()).append((" "); }");
            // Store the attribute name and return type to PTS
            pts.put(pn, method.getReturnType());
        } else if ((matcher = ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) {
            // if the method starts with set
            // Get the parameter typeClass<? > pt = method.getParameterTypes()[0];
            // Get the attribute name
            String pn = propertyName(matcher.group(1));
            // Generate attribute judgments and setter calls as shown in the following example:
            // if( $2.equals("name") ) { w.setName((java.lang.String)$3); return; }
            c1.append(" if( $2.equals(\"").append(pn).append("\") ){ w.").append(method.getName()).append("(").append(arg(pt, "$3")).append("); return; }"); pts.put(pn, pt); }}// Add NoSuchPropertyException exception throwing code
    c1.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class " + c.getName() + ". \ "); }");
    c2.append(" throw new " + NoSuchPropertyException.class.getName() + "(\"Not found property \\\"\"+$2+\"\\\" field or setter method in class " + c.getName() + ". \ "); }");

    // make class
    long id = WRAPPER_CLASS_COUNTER.getAndIncrement();
    // Create a class generator
    ClassGenerator cc = ClassGenerator.newInstance(cl);
    // Set the class name and superclass
    cc.setClassName((Modifier.isPublic(c.getModifiers()) ? Wrapper.class.getName() : c.getName() + "$sw") + id);
    cc.setSuperClass(Wrapper.class);

    // Add default constructor
    cc.addDefaultConstructor();
    // Add a field
    cc.addField("public static String[] pns;"); // property name array.
    cc.addField("public static " + Map.class.getName() + " pts;"); // property type map.
    cc.addField("public static String[] mns;"); // all method name array.
    cc.addField("public static String[] dmns;"); // declared method name array.
    for (int i = 0, len = ms.size(); i < len; i++) {
        cc.addField("public static Class[] mts" + i + ";");
    }

    // Add method
    cc.addMethod("public String[] getPropertyNames(){ return pns; }");
    cc.addMethod("public boolean hasProperty(String n){ return pts.containsKey($1); }");
    cc.addMethod("public Class getPropertyType(String n){ return (Class)pts.get($1); }");
    cc.addMethod("public String[] getMethodNames(){ return mns; }");
    cc.addMethod("public String[] getDeclaredMethodNames(){ return dmns; }");
    cc.addMethod(c1.toString());
    cc.addMethod(c2.toString());
    cc.addMethod(c3.toString());

    try {
        / / generated classClass<? > wc = cc.toClass();// setup static field.
        // Set the field value
        wc.getField("pts").set(null, pts);
        wc.getField("pns").set(null, pts.keySet().toArray(new String[0]));
        wc.getField("mns").set(null, mns.toArray(new String[0]));
        wc.getField("dmns").set(null, dmns.toArray(new String[0]));
        int ix = 0;
        for (Method m : ms.values()) {
            wc.getField("mts" + ix++).set(null, m.getParameterTypes());
        }
        // Create the Wrapper instance
        return (Wrapper) wc.newInstance();
    } catch (RuntimeException e) {
        throw e;
    } catch (Throwable e) {
        throw new RuntimeException(e.getMessage(), e);
    } finally{ cc.release(); ms.clear(); mns.clear(); dmns.clear(); }}Copy the code

This method is a bit long and can be roughly divided into several steps:

  1. Initialize c1, C2, C3, PTS, MS, MNS, DMNS variables and add method definitions and type conversion codes to C1, C2, and C3.
  2. Generates conditional values and assignment codes for public-level fields
  3. Generates judgment statements and method invocation statements for methods defined in the current class.
  4. Handles getters, setters, and methods that start with is/has/ CAN. This is handled using regular expressions to get the method type (get/set/is/…). , and the property name. A judgment statement is then generated for the attribute name, followed by an invocation statement for the method.
  5. Build the Class classes for the code you just generated with ClassGenerator and create objects with reflection. The ClassGenerator is wrapped by Dubbo itself. The core of this Class is toClass(ClassLoader, ProtectionDomain), an overloaded method of toClass() that builds the Class using JavAssist.

Services available

Service exposure is divided into exposure to local (JVM) and exposure to remote. The lower half of the doExportUrlsFor1Protocol() method is the logic that services expose. According to the configuration of scope, it is divided into:

  • Scope = none, does not expose the service
  • scope ! = remote, local exposure
  • scope ! = local, exposed to remote

Local exposure

The export locally executes the exportLocal() method in ServiceConfig.

ExportLocal () and (4)
private void exportLocal(URL url) {
    // If the protocol is not injvm
    if(! Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {// Generate the local URL, change the protocol to injvm, set host and port, respectively
        URL local = URLBuilder.from(url)
                .setProtocol(Constants.LOCAL_PROTOCOL)
                .setHost(LOCALHOST_VALUE)
                .setPort(0)
                .build();
        // Create invoker through the proxy project
        // Call the export method to expose the service and export is generatedExporter<? > exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass, local));// Add the generated exposes to the collection
        exporters.add(exporter);
        logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry"); }}Copy the code

The local exposure calls the InjVM protocol method, the export() method of InjvmProtocol.

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

This method simply creates one that is exposed locally and therefore in the same JVM. So you don’t need to do anything else.

Exposure to remote

The logic of remote exposure is much more complex than that of local exposure, which can be roughly divided into two processes: service exposure and service registration. Let’s start with service exposure. Note that dubbo has many protocol implementations, in doExportUrlsFor1Protocol(), after Invoker is generated, we need to call the Export () method of protocol. Many people would think that export() here is the method in the protocol implementation specified in the configuration, but this is not true. The export() method of RegistryProtocol implements both service exposure and service registration. So export() calls export() of the RegistryProtocol.

export()
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    // Get the url of the registry
    URL registryUrl = getRegistryUrl(originInvoker);
    // url to export locally
    // Get the registered service provider URL
    URL providerUrl = getProviderUrl(originInvoker);

    // Subscribe the override data
    // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call
    // the same service. Because the subscribed is cached key with the name of the service, it causes the
    // subscription information to cover.
    // Get override subscription URL
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    // Create a listener for override
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    // Add listeners to the collection
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);

    // Override the original URL according to override's configuration, making the configuration up to date.
    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    //export invoker
    // Service exposure
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

    // url to registry
    // Load the Registry implementation class based on the URL, such as ZookeeperRegistry
    final Registry registry = getRegistry(originInvoker);
    // Returns the url registered to the registry and filters the URL parameters once
    final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);
    Generate ProviderInvokerWrapper, which holds the invocation addresses and proxy objects of the service provider and consumer
    ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
            registryUrl, registeredProviderUrl);


    / / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    //to judge if we need to delay publish
    // Get the register parameter
    boolean register = registeredProviderUrl.getParameter("register".true);
    // If you need to register the service
    if (register) {
        // Register the service with the registry
        register(registryUrl, registeredProviderUrl);
        // Set reg to true to indicate service registration
        providerInvokerWrapper.setReg(true);
    }

    // Deprecated! Subscribe to override rules in 2.6.x or before.
    // Subscribe override data to the registry
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    // Set the registry URL
    exporter.setRegisterUrl(registeredProviderUrl);
    // Set the URL for the Override data subscription
    exporter.setSubscribeUrl(overrideSubscribeUrl);
    //Ensure that a new exporter instance is returned every time export
    // Create and return DestroyableExporter
    return new DestroyableExporter<>(exporter);
}
Copy the code

From the code point of view, I split it into two parts, service exposure and service registration. The logic of this method can be roughly divided into the following steps:

  1. Get the URL of the service provider, reconfigure the URL through the Override data, and perform doLocalExport() to expose the service.
  2. Load the registry implementation class to register services with the registry.
  3. Subscribe to override data to the registry.
  4. Create and return DestroyableExporter

The service exposure first calls the doLocalExport() method of the RegistryProtocol

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
    String key = getCacheKey(originInvoker);

    // add cache
    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
        // Create Invoker as a delegate objectInvoker<? > invokerDelegate =new InvokerDelegate<>(originInvoker, providerUrl);
        // Call protocol's export method to expose the service
        return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
    });
}
Copy the code

The logic here is relatively simple, mainly based on the different protocol configuration, call different protocol implementation. The same InjvmProtocol is implemented when exposed locally. I’m going to assume that the configuration uses the Dubbo protocol to continue.

DubboProtocol export ()

“Dubbo source code parsing (24) remote call — Dubbo protocol” (three) DubboProtocol export() related source analysis, from the source can be seen to do some local stubs, the key is openServer, to start the server.

DubboProtocol openServer ()

DubboProtocol has openServer() related source code analysis, but the code in this article is 2.6.x code, the latest version of the DCL. The reset method resets some configuration of the server. For example, on the same machine (single nic), only one server instance can be started on the same port. If a server instance exists on a port, the reset method is called to reset some configurations of the server. Focus on the createServer() method.

DubboProtocol createServer ()

DubboProtocol createServer(), the latest version of the default remote communication server implementation has been changed to Netty4. This method can be roughly divided into the following steps:

  1. Checks whether the server remote communication server implementation configuration is supported
  2. To create a server instance, call the bind() method
  3. Checks whether the configuration of the server remote communication client implementation is supported
Exchangers bind ()

Refer to dubbo source code analysis (10) Telecommunications – Exchange layer (21) Exchangers.

public static ExchangeServer bind(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, which is headerRecovery by default.
    // The bind method is then called to create an ExchangeServer instance
    return getExchanger(url).bind(url, handler);
}
Copy the code
HeaderExchanger bind ()

(3) The bind() method does the following steps:

  1. Create HeaderExchangeHandler
  2. Create DecodeHandler
  3. Transporters.bind() to create the server instance.
  4. Create HeaderExchangeServer

Which HeaderExchangeHandler, DecodeHandler, HeaderExchangeServer can refer to “dubbo source code analysis (ten) remote communication – Exchange layer” in the explanation.

Transporters bind() (6)
public static Server bind(URL url, ChannelHandler... handlers) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    if (handlers == null || handlers.length == 0) {
        throw new IllegalArgumentException("handlers == null");
    }
    ChannelHandler handler;
    if (handlers.length == 1) {
        handler = handlers[0];
    } else {
        // If the handlers element number is greater than 1, a ChannelHandler distributor is created
        handler = new ChannelHandlerDispatcher(handlers);
    }
    // Get the adaptive Transporter instance and call the instance method
    return getTransporter().bind(url, handler);
}
Copy the code

The Transporter obtained by the getTransporter() method is dynamically created at runtime. The class is called TransporterAdaptive, which is an adaptive extension class. TransporterAdaptive determines what type of Transporter to load at run time based on the URL parameter passed in. The default is a Netty4-based implementation. Let’s say it’s the Bind method of the NettyTransporter.

NettyTransporter bind() (6)

Dubbo source code analysis (17) Telecommunications — Netty4 (6) NettyTransporter.

public Server bind(URL url, ChannelHandler listener) throws RemotingException {
    / / create NettyServer
    return new NettyServer(url, listener);
}

Copy the code
NettyServer constructor (7)

Can refer to the “dubbo source code parsing (seventeen) telecommunications — Netty4” (five) NettyServer

public NettyServer(URL url, ChannelHandler handler) throws RemotingException {
    super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)));
}

Copy the code

The AbstractServer constructor is called

AbstractServer constructor (7)

See AbstractServer constructor in Dubbo Source Code Analysis (9) Remote Communication — Transport Layer (3).

AbstractServer doOpen is an abstract method in AbstractServer. AbstractServer doOpen() is an abstract method in AbstractServer.

NettyServer doOpen ()

You can refer to “dubbo source code analysis (seventeen) telecommunications – Netty4” in (five) NettyServer source analysis. When this is done, the server is turned on and the service is exposed. The next step is to explain service registration.

Service Registration (9)

Dubbo service registration is not required because dubbo supports direct connection to bypass the registry. Direct connection is often used for service testing.

Go back to the section below the partition line for the Export () method of RegistryProtocol. The service registration first calls the register() method.

RegistryProtocol register ()
public void register(URL registryUrl, URL registeredProviderUrl) {
    / / access to the Registry
    Registry registry = registryFactory.getRegistry(registryUrl);
    // Register service
    registry.register(registeredProviderUrl);
}

Copy the code

So service registration can be roughly divided into two steps:

  1. Get the registry instance
  2. The registration service

To obtain the registry, you first perform the Abstractregistry () method

AbstractRegistryFactory getRegistry ()

Dubbo registry: AbstractRegistryFactory: AbstractRegistryFactory: AbstractRegistryFactory: AbstractRegistryFactory: Abstract: CreateRegistry () is an abstract method. The implementation logic is done by subclasses. Suppose zooKeeper is used as the registry. CreateRegistry () of ZookeeperRegistryFactory is called.

ZookeeperRegistryFactory createRegistry ()
public Registry createRegistry(URL url) {
    return new ZookeeperRegistry(url, zookeeperTransporter);
}

Copy the code

Is to create a ZookeeperRegistry and execute the constructor of ZookeeperRegistry.

Constructor of ZookeeperRegistry

See dubbo source code Parsing (7) – Zookeeper (1) ZookeeperRegistry source code analysis. The general logic can be divided into the following steps:

  1. Example Creating a ZooKeeper client
  2. Adding listeners

Look at the ZookeeperTransporter connect method, because when the connect method completes, the registry creation process ends. The connect method is executed first AbstractZookeeperTransporter.

AbstractZookeeperTransporter connect ()
public ZookeeperClient connect(URL url) {
    ZookeeperClient zookeeperClient;
    // Get all urls
    List<String> addressList = getURLBackupAddress(url);
    // The field define the zookeeper server , including protocol, host, port, username, password
    // Find available clients from the cache, if any, return them directly
    if((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) ! =null && zookeeperClient.isConnected()) {
        logger.info("find valid zookeeper client from the cache for address: " + url);
        return zookeeperClient;
    }
    // avoid creating too many connections, so add lock
    synchronized (zookeeperClientMap) {
        if((zookeeperClient = fetchAndUpdateZookeeperClientCache(addressList)) ! =null && zookeeperClient.isConnected()) {
            logger.info("find valid zookeeper client from the cache for address: " + url);
            return zookeeperClient;
        }

        // Create a client
        zookeeperClient = createZookeeperClient(toClientURL(url));
        logger.info("No valid zookeeper client found from cache, therefore create a new client for url. " + url);
        // add cache
        writeToClientMap(addressList, zookeeperClient);
    }
    return zookeeperClient;
}

Copy the code

Look at the source code, mainly performed createZookeeperClient () method, and the method is an abstract method, by a subclass implementation, here is CuratorZookeeperTransporter createZookeeperClient ()

CuratorZookeeperTransporter createZookeeperClient ()
public ZookeeperClient createZookeeperClient(URL url) {
    return new CuratorZookeeperClient(url);
}

Copy the code

Here is the constructor that executes the CuratorZookeeperClient.

CuratorZookeeperClient constructor

Dubbo source code parsing (18) remote communication — Zookeeper (4) CuratorZookeeperClient source code analysis, which logic is mainly used to create and start the CuratorFramework instance, Basically all apis that call the Curator framework.

After creating an instance of the registry, we are ready to register the service. That is, the FailbackRegistry register() method is called.

FailbackRegistry register ()

Refer to FailbackRegistry for source code analysis under the support package in dubbo source code parsing (iii) registry – opening chapter. You can see that the key is to execute the doRegister() method, which is an abstract method done by subclasses. Because it is assumed to be ZooKeeper, doRegister() of ZookeeperRegistry is executed.

ZookeeperRegistry doRegister ()

You can refer to the source code of ZookeeperRegistry in Dubbo source Code Analysis (7) — ZooKeeper. You can see that the logic is to call the ZooKeeper client to create a service node. The node path is generated by the toUrlPath method. In this case, the create method executes the AbstractZookeeperClient create() method

AbstractZookeeperClient the create ()

See Dubbo source code analysis (18) Remote Communication — Zookeeper (2) AbstractZookeeperClient source code analysis. CreateEphemeral () and createPersistent() are abstract methods that are implemented by subclasses, the CuratorZookeeperClient class. The code logic is simple. I won’t bore you with it. At this point, the service is registered.

The rules for subscribing to override data to the registry have changed significantly in the latest version, and are different from 2.6.x and previous versions. So this is covered in new features.

Afterword.

Please refer to the official documentation: dubbo.apache.org/zh-cn/docs/…

This article explains the service exposure process of Dubbo, which is also a preparation for the introduction of 2.7 new features. The next article explains the service reference process.