dubbo Service Exposure Process
Objective: To analyze the service exposure process from the source point of view.
Preface
Originally this article was about asynchronous transformation, but recently I've been thinking about how to write a part of the optimization transformation to make it more understandable to the reader.I think it's necessary to start with the whole call chain, find out when the function happens, say something general, when or what scene the code was executed, then analyze how it was implemented internally, and finally explain the benefits of this transformation.
I rarely mentioned the relationship between call chains in the previous articles, nor do the modules be strung together, and to explain the asynchronous transformation I think it is necessary to understand the process of exposing and referencing services first, so I will use two articles to explain the process of exposing and referencing services.
Service Exposure Process
The process of service exposure can be roughly divided into three parts:
- Pre-work, mainly for checking parameters and assembling URL s.
- Exporting a service involves exposing the service locally (JVM) and remotely.
- Register a service with the registry for service discovery.
Exposure Start Point
Spring has an ApplicationListener interface that defines an onApplicationEvent() method that is triggered when any event occurs within a 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, print the log if (!isExported() && !isUnexported()) { if (logger.isInfoEnabled()) { logger.info("The service ready on spring started. service: " + getInterface()); } // export export(); } }
Services are exposed as long as they are not exposed and services are not unexposed.Execute the export method.The next step is related to the time series diagram of the official website. Look at the process below against the time series diagram.
I will add the subtitle below with the scope of each step of the time series diagram, for example, the preceding work mentioned below is actually 1:export() in the time series diagram, so I will write 1 in the heading brackets.
The eighth step in service exposure is no longer available.
Pre-job (1)
The front-end work mainly consists of two parts, configuration checking and URL assembly.Before exposing the service, Dubbo needs to check that the user's configuration is reasonable or supplement the default configuration for the user.Once the configuration checks are complete, you need to assemble the URLs based on these configurations.URLs play an important role in Dubbo.Dubbo uses URLs as configuration vectors, and all the extensions are to obtain the configuration through URLs.
Configuration Check
After calling the export method, the export method in ServiceConfig is executed.
public synchronized void export() { //Check and update the configuration checkAndUpdateSubConfigs(); // If it should not be exposed, end directly if (!shouldExport()) { return; } // Exposing services after delaying if delayed loading is used if (shouldDelay()) { delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS); } else { // Exposure Services doExport(); } }
You can see that the first thing to do is to check and update the configuration, which executes the checkAndUpdateSubConfigs method in ServiceConfig.It then detects whether it should be exposed, or if it should not, ends directly, and then detects whether delayed loading is configured, and if so, uses a timer for the purpose of delayed loading.
checkAndUpdateSubConfigs()
public void checkAndUpdateSubConfigs() { // Use default configs defined explicitly on global configs // Used to detect if core configuration class objects such as provider, application, etc. are empty. // If empty, try to get an instance from another configuration class object. completeCompoundConfigs(); // Config Center should always being started first. // Open Configuration Center startConfigCenter(); // Detects if the provider is empty, creates a new one, and initializes it with a system variable checkDefault(); // Check if the application is empty checkApplication(); // Check if the registry is empty checkRegistry(); // Check if protocols are empty checkProtocol(); this.refresh(); // Check if metadata center configuration is empty checkMetadataReport(); // Service interface name cannot be empty, otherwise an exception is thrown if (StringUtils.isEmpty(interfaceName)) { throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!"); } // Detect if ref is a generalized service type if (ref instanceof GenericService) { // Set interfaceClass to GenericService interfaceClass = GenericService.class; if (StringUtils.isEmpty(generic)) { // Set generic = true generic = Boolean.TRUE.toString(); } } else { try { // Get 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 <dubbo:method>tags checkInterfaceAndMethods(interfaceClass, methods); // Testing ref legality checkRef(); generic = Boolean.FALSE.toString(); } // Stub locals are also configured with local stubs 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); } } // Local stub validity check checkStubAndLocal(interfaceClass); // mock validity check checkMock(interfaceClass); }
As you can see, this method checks various configurations and updates some of them.I will not expand on the details of the checks, because the entire process of service exposure is the focus of this article.
After shouldExport() and shouldDelay() method detection, the doExport() method of ServiceConfig is executed
doExport()
protected synchronized void doExport() { // 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 exposed, it ends directly if (exported) { return; } // Settings Exposed exported = true; // Assign interface name if path is empty if (StringUtils.isEmpty(path)) { path = interfaceName; } // Multi-Protocol Multi-Registry Exposure Service doExportUrls(); }
This method is to support multi-protocol, multi-registry exposure of services by performing a check and then the doExportUrls() method of ServiceConfig.
doExportUrls()
private void doExportUrls() { // Load Registry Link List<URL> registryURLs = loadRegistries(true); // Traverse protocols and expose services under each protocol for (ProtocolConfig protocolConfig : protocols) { // Use path, group, version as key for service uniqueness determination 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 URL doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }
You can see from this method:
- The loadRegistries() method is to load the registry link.
- The uniqueness of a service is determined by a combination of path, group, and version.
- The doExportUrlsFor1Protocol() method starts assembling the URL.
loadRegistries()
protected List<URL> loadRegistries(boolean provider) { // check && override if necessary List<URL> registryList = new ArrayList<URL>(); // If registries are empty, return the empty collection directly if (CollectionUtils.isNotEmpty(registries)) { // Traversing the registry configuration collection registries for (RegistryConfig config : registries) { // Get Address String address = config.getAddress(); // Set to 0.0.0.0 if address is empty if (StringUtils.isEmpty(address)) { address = Constants.ANYHOST_VALUE; } // If the address is N/A, skip 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 RegistryConfig field information to map appendParameters(map, config); // Add path map.put(Constants.PATH_KEY, RegistryService.class.getName()); // Add protocol version, release version, timestamp, etc. to map appendRuntimeParameters(map); // If there is no protocol in the map, the default is to use the dubbo protocol if (!map.containsKey(Constants.PROTOCOL_KEY)) { map.put(Constants.PROTOCOL_KEY, Constants.DUBBO_PROTOCOL); } // Resolves to a list of URLs, address may contain more than one registry ip, so it resolves to a list of URLs List<URL> urls = UrlUtils.parseURLs(address, map); // Traverse the list of URL s for (URL url : urls) { // Set URL protocol header to registry url = URLBuilder.from(url) .addParameter(Constants.REGISTRY_KEY, url.getProtocol()) .setProtocol(Constants.REGISTRY_PROTOCOL) .build(); // By judging the criteria, you decide whether to add a url to the registryList under the following conditions: // If it's a service provider, it's a registry service or a consumer side, and it's a subscription service // Join the registryList if ((provider && url.getParameter(Constants.REGISTER_KEY, true)) || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) { registryList.add(url); } } } } } return registryList; }
Assembly URL
As I mentioned in previous articles, dubbo uses URLs to carry various configurations throughout the call chain, which is the carrier of configurations.This is where service configurations are assembled into URLs. Traversing through each protocol configuration mentioned above, exposing the service under each protocol, executes the doExportUrlsFor1 Protocol () method of ServiceConfig, which implements the logic of assembling URLs in the first half and exposing dubbo services in the second half, separated by a splitter line.
doExportUrlsFor1Protocol()
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { // Get Protocol Name String name = protocolConfig.getName(); // If empty, the default dubbo if (StringUtils.isEmpty(name)) { name = Constants.DUBBO; } Map<String, String> map = new HashMap<String, String>(); // Set up a service provider book map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE); // Add protocol version, release version, timestamp, etc. to map appendRuntimeParameters(map); // Add all the information for metrics, application, module, provider, protocol s to the map appendParameters(map, metrics); appendParameters(map, application); appendParameters(map, module); appendParameters(map, provider, Constants.DEFAULT_KEY); appendParameters(map, protocolConfig); appendParameters(map, this); // If the configuration list for method is not empty if (CollectionUtils.isNotEmpty(methods)) { // Traversing the method configuration list for (MethodConfig method : methods) { // Add method name to map appendParameters(map, method, method.getName()); // Add the field information of the MethodConfig object to the map, key = method name. property name. // For example, store <dubbo:method name="sayHello" retries="2">corresponding MethodConfig, // 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, do not retry, set 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 // //Detect whether the type property is empty or an empty string if (argument.getType() != null && argument.getType().length() > 0) { // Get all collection of methods for the service using reflection Method[] methods = interfaceClass.getMethods(); // visit all methods if (methods != null && methods.length > 0) { // Traverse all methods for (int i = 0; i < methods.length; i++) { // Get method name String methodName = methods[i].getName(); // target the method, and get its signature // Find the target method if (methodName.equals(method.getName())) { // Array of parameter types for the target method by reflection Class<?>[] argtypes = methods[i].getParameterTypes(); // one callback in the method // If the subscript is -1 if (argument.getIndex() != -1) { // Detect if the argType name matches the type attribute in ArgumentConfig if (argtypes[argument.getIndex()].getName().equals(argument.getType())) { // Add ArgumentConfig field information to map appendParameters(map, argument, method.getName() + "." + argument.getIndex()); } else { // Inconsistency throws an exception 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 // Traverse through the parameter type array argtypes to find parameters 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 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 configures the index attribute, and the index!= -1 is added directly to the map appendParameters(map, argument, method.getName() + "." + argument.getIndex()); } else { // throw throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>"); } } } } // end of methods for } // If it is a generalization call, set generic and methods in the map if (ProtocolUtils.isGeneric(generic)) { map.put(Constants.GENERIC_KEY, generic); map.put(Constants.METHODS_KEY, Constants.ANY_VALUE); } else { // Get Version Number String revision = Version.getVersion(interfaceClass, version); // Put in map if (revision != null && revision.length() > 0) { map.put(Constants.REVISION_KEY, revision); } // Get a collection of methods String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); // Alert if empty 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 collection map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ",")); } } // Add the token value to the 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 Address String host = this.findConfigedHosts(protocolConfig, registryURLs, map); // Get the port number Integer port = this.findConfigedPorts(protocolConfig, name, map); // Generate URL s URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map); // _________161 // Load the ConfiguratorFactory and generate an instance of Configurator to determine if an implementation of the protocol exists if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) .hasExtension(url.getProtocol())) { // Configuring URLs through instances 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, exposed locally if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) { // Exposure to Local 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 collection is not empty if (CollectionUtils.isNotEmpty(registryURLs)) { // Traverse Registry for (URL registryURL : registryURLs) { // Add dynamic configuration url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY)); // Load Monitor Link URL monitorUrl = loadMonitor(registryURL); if (monitorUrl != null) { // Add 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 Agent Method String proxy = url.getParameter(Constants.PROXY_KEY); if (StringUtils.isNotEmpty(proxy)) { // Add proxy to registry to url registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy); } // Generate Invoker for Service Provider Class (ref) Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); // DelegateProviderMetaDataInvoker is used to hold Invoker and ServiceConfig DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); // Expose the service and generate the Exporter Exporter<?> exporter = protocol.export(wrapperInvoker); // Join the Exposer Collection exporters.add(exporter); } } else { // No registry exists, only services are exposed, no address exposure is recorded Invoker<?> 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 metadata center's url if ((metadataReportService = getMetadataReportService()) != null) { metadataReportService.publishProvider(url); } } } this.urls.add(url); }
Looking at the part on the split line first, that is, the whole process of assembling the URL, I think it can be roughly divided into the following steps:
- It puts all the configurations of metrics, application, module, provider, protocol into the map.
- For each method configuration, a signature check is made first to find out if there is a method for configuring the service. Then, if there is a parameter for the method signature, the method configuration is checked successfully before adding the method configuration to the map.
- Add information such as generalized calls, version numbers, method s, token s, etc. to the map
- Obtain the service exposure address and port number, and assemble the URL using the data in the map.
Create invoker (2,3)
Exposed to remote source directly look at the lower half of the line split by the doExportUrlsFor1Protocol() method.When an exposer is generated, the service is exposed, and the internal process of exposing is analyzed in detail.You can find that invokers are created through a proxy factory whether exposed locally or remotely.This brings you to the ProxyFactory of the time series diagram above.I mentioned invoker in this article: dubbo Source Parsing (19) Remote Calls - Beginning First, let's see how invoker was born.Invoker was created by ProxyFactory, and the default ProxyFactory implementation class for Dubbo is JavassistProxyFactory.There is a getInvoker() method in JavassistProxyFactory.
Get 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 '$' // //Create a Wrapper for the target class final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); // Create anonymous Invoker class objects 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 { // Invoke Wrapper's invokeMethod method, which eventually calls the target method return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; }
You can see that this method 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 by the getWrapper(Class) method.During the creation of a Wrapper subclass, the subclass code generation logic parses the Class object passed in by the getWrapper method to get information such as class methods, class member variables, and so on.And generate invokeMethod method code and some other method code.Once the code is generated, the Class object is generated through Javassist, and the Wrapper instance is created through reflection.So let's first look at the getWrapper() method:
getWrapper()
public static Wrapper getWrapper(Class<?> c) { while (ClassGenerator.isDynamicClass(c)) // can not wrapper on dynamic class. { // Returns the superclass of the object c = c.getSuperclass(); } // Returns the subclass Wrapper if the superclass is Object if (c == Object.class) { return OBJECT_WRAPPER; } // Get an instance of Wrapper from the cache Wrapper ret = WRAPPER_MAP.get(c); // Create Wrapper if no hits if (ret == null) { // Create Wrapper ret = makeWrapper(c); // Write Cache WRAPPER_MAP.put(c, ret); } return ret; }
This method only caches Wrapper.The main logic is makeWrapper().
makeWrapper()
// Check if c is a base type and throw an exception if it is if (c.isPrimitive()) { throw new IllegalArgumentException("Can not create wrapper for primitive type: " + c); } // Get Class Name String name = c.getName(); // Get Class Loader ClassLoader cl = ClassHelper.getClassLoader(c); // c1 is used to store setPropertyValue method code StringBuilder c1 = new StringBuilder("public void setPropertyValue(Object o, String n, Object v){ "); // c2 is used to store getPropertyValue method code StringBuilder c2 = new StringBuilder("public Object getPropertyValue(Object o, String n){ "); // c3 is used to store 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 capture 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 types Map<String, Class<?>> pts = new HashMap<>(); // <property name, property types> // ms is used to store Method description information (which can be understood as Method signatures) 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 are 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. // Gets the fields of the public access level and generates conditional judgment 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 keyword static or transient ly modified variables continue; } // Generate conditional judgments 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 judgments 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 methods of class c Method[] methods = c.getMethods(); // get all public method. // Methods that detect whether c contains declarations in the current class boolean hasMethod = hasMethods(methods); // If Included 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 name of the method String mn = m.getName(); // Generate method name judgment statements, such as: // if ( "sayHello".equals( $2 ) c3.append(" if( \"").append(mn).append("\".equals( $2 ) "); int len = m.getParameterTypes().length; // Generate a "Number of parameters passed in at runtime and length of method parameter list" judgment statement, such as: // && $3.length == 2 c3.append(" && ").append(" $3.length == ").append(len); boolean override = false; for (Method m2 : methods) { // Detect whether there is overload in the method 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, so you can't just tell if the two methods are equal. // The parameter type of the method needs to be further determined 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, and the generated code may be as follows (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 target method call statements based on 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 method name to mns collection mns.add(mn); // Detects if the current method is declared in c if (m.getDeclaringClass() == c) { // If so, add the current method name to the dmns dmns.add(mn); } ms.put(ReflectUtils.getDesc(m), m); } // Add exception capture statement c3.append(" } catch(Throwable e) { "); c3.append(" throw new java.lang.reflect.InvocationTargetException(e); "); c3.append(" }"); } // Add NoSuchMethodException exception c3.append(" throw new " + NoSuchMethodException.class.getName() + "(\"Not found method \\\"\"+$2+\"\\\" in class " + c.getName() + ".\"); }"); // deal with get/set method. Matcher matcher; // Processing get/set methods for (Map.Entry<String, Method> entry : ms.entrySet()) { // Get method name String md = entry.getKey(); // Get Method Method Method method = entry.getValue(); // If the method starts with get if ((matcher = ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { // Get Property Name String pn = propertyName(matcher.group(1)); // Generate attribute judgments and return statements as follows: // 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 property names and return types 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 type Class<?> pt = method.getParameterTypes()[0]; // Get property name String pn = propertyName(matcher.group(1)); // Generate attribute judgments and setter call statements as follows: // 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 throw 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 Class Generator ClassGenerator cc = ClassGenerator.newInstance(cl); // Set 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 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 { // Generate Class Class<?> wc = cc.toClass(); // setup static field. // Setting field values 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 a 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(); } }
This method is a bit long and can be roughly divided into several steps:
- Initialize c1, c2, c3, pts, ms, mns, dmns variables, add method definitions and type conversion codes to c1, c2, c3.
- Generate conditional evaluation and assignment codes for public-level fields
- Generate judgement statements and method call statements for methods defined in the current class.
- Processing getter s, setter s, and methods starting with is/has/can.This is handled by getting the method type (get/set/is/...) and the property name through a regular expression.A judgment statement is then generated for the property name and a call statement for the method.
- Class classes are built from ClassGenerator for the code just generated, and objects are created from reflection.ClassGenerator is encapsulated by Dubbo itself, and its core is the overloaded method toClass(ClassLoader, ProtectionDomain) of toClass(), which builds a Class from javassist.
Service Exposure
Service exposure is divided into local exposure (JVM) and remote exposure.The doExportUrlsFor1Protocol() method splits the lower half of the line into the logic of service exposure.The scope s are divided into:
- scope = none, do not expose service
- Scope!= remote, exposed locally
- Scope!= local, exposed to remote
Exposure to Local
Export locally executes the exportLocal() method in ServiceConfig.
exportLocal()(4)
private void exportLocal(URL url) { // If the protocol is not injvm if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { // Generate local url s, change 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 proxy project // Call the export method again to expose the service and generate the Exporter Exporter<?> exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass, local)); // Add generated exposers to the collection exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry"); } }
Local exposure calls the injvm protocol method, the export() method of the InjvmProtocol.
export()(5)
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap); }
This method only creates one because it is exposed locally and therefore in the same jvm.No other action is required.
Exposure to Remote
The logic of exposing to remote is more complex than local, and it can be roughly divided into two processes: service exposure and service registration.Let's first look at service exposure.We know that dubbo has many protocol implementations. Once an Invoker is generated in the lower half of the split line of the doExportUrlsFor1 Protocol () method, you need to call the export() method of the protocols. Many people will think that export() here is the method in the protocol implementation specified in the configuration, but this is incorrect.Because service registration is required after exposing to remote locations, the export() method of RegistryProtocols implements two processes of service exposure and service registration.So here export() calls export() of 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 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 the override subscription URL final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl); // Create an override listener final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker); // Add listeners to the collection overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); // Override the original url according to the override configuration so that the configuration is up-to-date. providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener); //export invoker // Service Exposure final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl); // url to registry // Load Registry implementation classes based on URL s, such as ZookeeperRegistry final Registry registry = getRegistry(originInvoker); // Return the url registered with the registry and filter the url parameter once final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl); // Generate ProviderInvokerWrapper, which saves call addresses and proxy objects for service providers and consumers ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl); // __________ //to judge if we need to delay publish // Get register parameter boolean register = registeredProviderUrl.getParameter("register", true); // If you need to register a service if (register) { // Register services with the registry register(registryUrl, registeredProviderUrl); // Setting reg to true indicates service registration providerInvokerWrapper.setReg(true); } // Deprecated! Subscribe to override rules in 2.6.x or before. // Subscribe to the registry for override data registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); // Set registry url exporter.setRegisterUrl(registeredProviderUrl); // Set url for 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); }
From a code point of view, I divide the line into two parts, service exposure and service registration.The logic of this method is roughly divided into the following steps:
- Obtain the url of the service provider, reconfigure the url with override data, and execute doLocalExport() to expose the service.
- Load the registry implementation class to register the service with the registry.
- Subscribe to the registry for override data.
- Create and return DestroyableExporter
Service exposure calls the doLocalExport() method of RegistryProtocol first
private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) { String key = getCacheKey(originInvoker); // Join Cache return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> { // Create Invoker as a delegate class object Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl); // export method calling protocol to expose service return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker); }); }
The logic here is simple, mainly because different protocol implementations are called here according to different protocol configurations.Implement InjvmProtocol when exposed locally.Let me continue with the following discussion, assuming that the configuration is based on the dubbo protocol.
export() of DubboProtocol
dubbo Source Resolution (24) Remote Call - dubbo Protocol (3) DubboProtocol s have export() related source code analysis, from the source code you can see that some local stub processing has been done, the key is openServer to start the server.
openServer() for DubboProtocol
dubbo Source Resolution (24) Remote Call - dubbo Protocol (3) DubboProtocol s have open Server () related source code analysis, but the code in this article is 2.6.x, and DCL has been added to the latest version.The reset method resets some of the server's configurations.For example, on the same machine (single network card), only one server instance is allowed to start on the same port.If a server instance already exists on a port, the reset method is called to reset some of the server's configuration.The main focus is on the createServer() method.
createServer() of DubboProtocol
dubbo Source Resolution (24) Remote Call - dubbo Protocol (3) DubboProtocol s have source code analysis related to createServer(), and 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:
- Detection of support for server-side remote communication server implementation configuration
- Create a server instance, that is, call the bind() method
- Detection of support for server-side remote communication client implementation configuration
Exchangers'bind()
Can be referred to dubbo Source Resolution (10) Remote Communication - Exchange Layer (21) Exchangers in.
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"); // Gets Exchanger, which defaults to HeaderExchanger. // Next, call HeaderExchanger's bind method to create an ExchangeServer instance return getExchanger(url).bind(url, handler); }
Bid() of HeaderExchanger
Can be referred to dubbo Source Resolution (10) Remote Communication - Exchange Layer (16) HeaderExchanger, in which the bind() method takes roughly the following steps:
- Create HeaderExchangeHandler
- Create DecodeHandler
- Transporters.bind(), create a server instance.
- Create HeaderExchangeServer
HeaderExchangeHandler, DecodeHandler, HeaderExchangeServer are available for reference dubbo Source Resolution (10) Remote Communication - Exchange Layer Explanation in.
Bid() (6) of Transporters
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 { // Create ChannelHandler Distributor if the number of handlers elements is greater than 1 handler = new ChannelHandlerDispatcher(handlers); } // Get an adaptive Transporter instance and call the instance method return getTransporter().bind(url, handler); }
The Transporter obtained by the getTransporter() method is dynamically created at runtime with the class name TransporterAdaptive, or Adaptive Extension Class.TransporterAdaptive determines what type of Transporter to load at runtime based on the URL parameters passed in, defaulting to a Netty4-based implementation.Assume NettyTransporter's bind method.
Bid() (6) of NettyTransporter
Can be referred to dubbo Source Parsing (17) Remote Communication - Netty4 (6) NettyTransporter.
public Server bind(URL url, ChannelHandler listener) throws RemotingException { // Create NettyServer return new NettyServer(url, listener); }
Construction method of NettyServer (7)
Can be referred to dubbo Source Parsing (17) Remote Communication - Netty4 (5) NettyServer
public NettyServer(URL url, ChannelHandler handler) throws RemotingException { super(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME))); }
Called the parent AbstractServer construction method
Constructing AbstractServer (7)
Can be referred to dubbo Source Parsing (9) Remote Communication - Transport Layer (3) Construction methods in AbstractServer.
When the server instance is created, the server is opened. Is doOpen in AbstractServer an abstract method or a netty4 explanation, that is, the method of doOpen() in NettyServer?
doOpen() of NettyServer
Can be referred to dubbo Source Parsing (17) Remote Communication - Netty4 (5) Source code analysis in NettyServer.When execution is complete here, the server is turned on and the service is exposed.The next step is to explain the content of service registration.
Service Registration (9)
dubbo service registration is not required because dubbo supports direct connections to bypass the registry.Direct connections are often used for service testing.
Go back and look below the split line of the RegistryProtocol export() method.Where service registration calls the register() method first.
register() of RegistryProtocol
public void register(URL registryUrl, URL registeredProviderUrl) { // Get Registry Registry registry = registryFactory.getRegistry(registryUrl); // Registration Services registry.register(registeredProviderUrl); }
Therefore, service registration can be roughly divided into two steps:
- Get Registry Instance
- Registration Services
Getting the registry first executes the getRegistry() method of AbstractRegistryFactory
getRegistry() of AbstractRegistryFactory
Can be referred to dubbo Source Parsing (3) Registry - Beginning (7) Source parsing in AbstractRegistryFactory under the support package.The general logic is to fetch from the cache first and create a registry instance if no hits are made, where createRegistry() is an abstract method and the implementation logic is done by subclasses. Assuming zookeeper is used as the registry here, the createRegistry() of ZookeeperRegistryFactory is called.
createRegistry() of ZookeeperRegistryFactory
public Registry createRegistry(URL url) { return new ZookeeperRegistry(url, zookeeperTransporter); }
It creates a ZookeeperRegistry and executes the construction method of ZookeeperRegistry.
Construction method of ZookeeperRegistry
Can be referred to dubbo Source Resolution (7) Registry - zookeeper (1) Source code analysis in ZookeeperRegistry.The general logic can be divided into the following steps:
- Create zookeeper client
- Add listener
Look primarily at ZookeeperTransporter's connect method, because when the connect method is executed, the registry creation process ends.First, the connect method of AbstractZookeeperTransporter is executed.
connect() of AbstractZookeeperTransporter
public ZookeeperClient connect(URL url) { ZookeeperClient zookeeperClient; // Get all url addresses List<String> addressList = getURLBackupAddress(url); // The field define the zookeeper server , including protocol, host, port, username, password // Find available clients from the cache and return directly if any 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 Client zookeeperClient = createZookeeperClient(toClientURL(url)); logger.info("No valid zookeeper client found from cache, therefore create a new client for url. " + url); // Join Cache writeToClientMap(addressList, zookeeperClient); } return zookeeperClient; }
Looking at the source code above, the createZookeeperClient() method, which is an abstract method implemented by a subclass, is primarily executed by the createZookeeperClient() of CuratorZookeeperTransporter.
createZookeeperClient() of CuratorZookeeperTransporter
public ZookeeperClient createZookeeperClient(URL url) { return new CuratorZookeeperClient(url); }
Here's how CuratorZookeeperClient is constructed.
Construction method of CuratorZookeeperClient
Can be referred to dubbo Source Resolution (18) Remote Communication - Zookeeper (4) Source code analysis in Curator ZookeeperClient, where logic is mainly used to create and launch CuratorFramework instances, basically the API that calls the Curator Framework.
Once we have created an instance of the registry, we are ready to register.That is, the register() method of FailbackRegistry is called.
FailbackRegistry's register()
Can be referred to dubbo Source Parsing (3) Registry - Beginning (6) Source code analysis in FailbackRegistry under support package.You can see that the key is to execute the doRegister() method, which is an abstract method and is done by subclasses.Here, because zookeeper is assumed, the doRegister () of ZookeeperRegistry is executed.
ZookeeperRegistry's doRegister()
Can be referred to dubbo Source Resolution (7) Registry - zookeeper (1) Source code in ZookeeperRegistry, you can see that the logic is to call the Zookeeper client to create a service node.Node paths are generated by the toUrlPath method.The create method here executes the create() method of AbstractZookeeperClient
create() of AbstractZookeeperClient
Can be referred to dubbo Source Parsing (18) Remote Communication - Zookeeper (2) Source code analysis in AbstractZookeeperClient.createEphemeral() and createPersistent() are abstract methods that are implemented by subclasses, the CuratorZookeeperClient class.The code logic is simple.I won't go into that again.Up to this point, the service is registered.
The rules for subscribing to override data to the registry have changed considerably in the latest version, unlike 2.6.x and before.So this part is explained in the new features.
Postnote
Refer to official documents: https://dubbo.apache.org/zh-c...
This article explains dubbo's service exposure process, but also paves the way for the following 2.7 new features, the next section explains the service reference process.