1. Introduction of Dubbo Service Introduction
The first two articles Export of Dubbo's Service Export 1 to Local,Export of Dubbo's Service Export 2 to Remote The process of service export is analyzed in detail. In this article, we will continue to analyze the Service Citation process. In Dubbo, we can refer to remote services in two ways. The first is to refer to services directly, and the second is to refer to services based on registries. Direct service connection is only suitable for debugging or testing service scenarios, not for online environment. Therefore, I will focus on the process of referencing services through registries. Obtaining service configuration from the registry is only one part of the service reference process. In addition, service consumers need to go through the steps of Invoker creation and proxy class creation. These steps will be analyzed in subsequent chapters.
2, Service Citation principle
There are two times when Dubbo service references occur. The first is when the Spring container invokes the afterPropertiesSet method of ReferenceBean, and the second is when the corresponding service of ReferenceBean is injected into other classes. The difference between the two referencing services is that the first is hungry and the second is lazy. By default, Dubbo uses a lazy reference service. If you need to use hungry Chinese style, you can configure the init attribute of < dubbo: reference > to turn it on. Let's start with the getObject method of ReferenceBean by analyzing the default configuration of Dubbo. When our services are injected into other classes, Spring calls the getObject method for the first time and executes the service reference logic by that method. According to the usual practice, the configuration inspection and collection work should be carried out before the specific work is carried out. Then according to the information collected, there are three ways to determine the service. The first is to refer to the local (JVM) service, the second is to refer to the remote service through direct connection, and the third is to refer to the remote service through the registry. Either way, you end up with an Invoker instance. If you have multiple registries and multiple service providers, you will get a set of Invoker instances, which need to be merged into one instance through the cluster management class Cluster. The merged Invoker instance already has the ability to call local or remote services, but it cannot be exposed to users, which can cause intrusion into user business code. At this point, the framework also needs to generate proxy classes for service interfaces through the ProxyFactory class, and let the proxy class call Invoker logic. It avoids the intrusion of Dubbo framework code into business code and makes the framework easier to use.
The above is the general principle of Service Citation. Let's go deep into the code and analyze the details of Service Citation in detail.
3. Source code analysis
3.1. Configuration Check and Collection
// ReferenceBean implements the InitializingBean interface, so this method is invoked when the Spring container is initialized, which corresponds to the first reference opportunity mentioned above. // By default, the reference service method does not follow here, and you need to configure init=true @Override public void afterPropertiesSet() throws Exception { // Delete some code // There are two times for Dubbo service reference, the first is to call the afterProperties Set of Reference Bean in the Spring container // Method refers to services, and the second refers to services corresponding to ReferenceBean when they are injected into other classes. The timing of these two referencing services // The difference is that the first is hungry and the second is lazy. By default, Dubbo uses lazy reference services. If needed // Hungry Chinese style can be opened by configuring the init attribute of <dubbo:reference>. Boolean b = isInit(); if (b == null && getConsumer() != null) { b = getConsumer().isInit(); } if (b != null && b.booleanValue()) { getObject(); } }
/** * The whole analysis process starts with the getObject method of ReferenceBean. When our services are injected into other classes, * Spring The getObject method is called the first time, and the service reference logic is executed by this method. */ @Override public Object getObject() throws Exception { return get(); } public synchronized T get() { if (destroyed) { throw new IllegalStateException("Already destroyed!"); } // If ref is empty or not, it is created by init method. if (ref == null) { // The init method is mainly used to handle configuration and to call createProxy to generate proxy classes. init(); } return ref; }
The init method is relatively long. In order to typeset, it is divided into several sections and deletes code such as exception capture and log record. The core code is ref = createProxy(map).
private void init() { // Avoid repeated initialization if (initialized) { return; } initialized = true; // Check interface legitimacy if (interfaceName == null || interfaceName.length() == 0) { throw new IllegalStateException("xxx"); } // Get the global configuration of consumer checkDefault(); appendProperties(this); if (getGeneric() == null && getConsumer() != null) { setGeneric(getConsumer().getGeneric()); }
// Detecting whether it is a generalization interface if (ProtocolUtils.isGeneric(getGeneric())) { interfaceClass = GenericService.class; } else { try { // Loading class interfaceClass = Class.forName(interfaceName, true, Thread.currentThread() .getContextClassLoader()); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } checkInterfaceAndMethods(interfaceClass, methods); }
// Get the attribute value corresponding to the interface name from the system variable String resolve = System.getProperty(interfaceName); String resolveFile = null; if (resolve == null || resolve.length() == 0) { // Getting parse file paths from system properties resolveFile = System.getProperty("dubbo.resolve.file"); // Load the configuration file from the specified location if (resolveFile == null || resolveFile.length() == 0) { File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties"); if (userResolveFile.exists()) { // Get the absolute path of the file resolveFile = userResolveFile.getAbsolutePath(); } }
if (resolveFile != null && resolveFile.length() > 0) { Properties properties = new Properties(); FileInputStream fis = null; try { fis = new FileInputStream(new File(resolveFile)); // Load configuration from file properties.load(fis); } // Get the configuration corresponding to the interface name resolve = properties.getProperty(interfaceName); } } if (resolve != null && resolve.length() > 0) { // Assign resolve to url for point-to-point direct connection url = resolve; }
if (consumer != null) { if (application == null) { application = consumer.getApplication(); } if (module == null) { module = consumer.getModule(); } if (registries == null) { registries = consumer.getRegistries(); } if (monitor == null) { monitor = consumer.getMonitor(); } } if (module != null) { if (registries == null) { registries = module.getRegistries(); } if (monitor == null) { monitor = module.getMonitor(); } }
if (application != null) { if (registries == null) { registries = application.getRegistries(); } if (monitor == null) { monitor = application.getMonitor(); } } // Detecting Application Legitimacy checkApplication(); // Detecting the validity of local stub configuration checkStubAndMock(interfaceClass); // Adding side, protocol version information, timestamp and process number to map, side indicates which side it is on and is currently on the service consumer side. Map<String, String> map = new HashMap<String, String>(); Map<Object, Object> attributes = new HashMap<Object, Object>(); map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE); map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()); map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis())); if (ConfigUtils.getPid() > 0) { map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid())); }
// Non-generalization services if (!isGeneric()) { // Get version String revision = Version.getVersion(interfaceClass, version); if (revision != null && revision.length() > 0) { map.put("revision", revision); } // Get a list of interface methods and add them to the map String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); if (methods.length == 0) { map.put("methods", Constants.ANY_VALUE); } else { map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ",")); } } map.put(Constants.INTERFACE_KEY, interfaceName); // Add field information of Application Config, ConsumerConfig, Reference Config and other objects to map appendParameters(map, application); appendParameters(map, module); appendParameters(map, consumer, Constants.DEFAULT_KEY); appendParameters(map, this);
// Like com.alibaba.dubbo.demo.DemoService String prefix = StringUtils.getServiceKey(map); if (methods != null && !methods.isEmpty()) { // Traversing the MethodConfig list for (MethodConfig method : methods) { appendParameters(map, method, method.getName()); String retryKey = method.getName() + ".retry"; // Check if map contains methodName.retry if (map.containsKey(retryKey)) { String retryValue = map.remove(retryKey); if ("false".equals(retryValue)) { // Configure methodName.retries by adding retries map.put(method.getName() + ".retries", "0"); } } // Add the Properties field in MethodConfig to attributes // For example, onreturn, onthrow, oninvoke, etc. appendAttributes(attributes, method, prefix + "." + method.getName()); checkAndConvertImplicitConfig(method, map, attributes); } }
// Get the service consumer's ip address String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY); if (hostToRegistry == null || hostToRegistry.length() == 0) { hostToRegistry = NetUtils.getLocalHost(); } else if (isInvalidLocalHost(hostToRegistry)) { throw new IllegalArgumentException(""); } map.put(Constants.REGISTER_IP_KEY, hostToRegistry); // Store attributes in the system context StaticContext.getSystemContext().putAll(attributes); // Create proxy classes, core ref = createProxy(map); // According to the service name, ReferenceConfig, the proxy class builds ConsumerModel. // And store ConsumerModel in Application Model ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods()); ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel); }
3.2. Create Invoker and Agent
The core code of the above init() method is ref = createProxy(map), which is analyzed below.
3.2.1. Overall analysis
// Literally, createProxy seems to be used only to create proxy objects, but in fact it is not. // This method also calls other methods to build and merge Invoker instances. private T createProxy(Map<String, String> map) { URL tmpUrl = new URL("temp", "localhost", 0, map); final boolean isJvmRefer; if (isInjvm() == null) { // The url configuration is specified without local reference. I understand that the url is used for direct connection. if (url != null && url.length() > 0) { isJvmRefer = false; } // Check whether local references are needed according to the protocol of url, scope, injvm and other parameters // For example, if the user explicitly configures scope=local, then isInjvmRefer returns true else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) { isJvmRefer = true; } else { isJvmRefer = false; } } else { // Get the injvm configuration value isJvmRefer = isInjvm().booleanValue(); }
// Local reference if (isJvmRefer) { // Generate local reference URL s, protocol injvm URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map); // Call refer method to build InjvmInvoker instance invoker = refprotocol.refer(interfaceClass, url); }
// Remote reference else { // The url is not empty, indicating that the user may want to make point-to-point calls if (url != null && url.length() > 0) { String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url); if (us != null && us.length > 0) { for (String u : us) { URL url = URL.valueOf(u); if (url.getPath() == null || url.getPath().length() == 0) { url = url.setPath(interfaceName); } if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))); } else { urls.add(ClusterUtils.mergeUrl(url, map)); } } } }
else { // Loading registry url List<URL> us = loadRegistries(false); if (us != null && !us.isEmpty()) { for (URL u : us) { URL monitorUrl = loadMonitor(u); if (monitorUrl != null) { map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString())); } // Add the refer parameter to the url and add the url to the urls urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))); } } // Registry not configured, exception thrown if (urls.isEmpty()) { // Throwing anomaly } }
// Single Registry or Service Provider (Direct Service Connection, the same below) if (urls.size() == 1) { // 1. Call Registry Protocol's refer to construct an Invoker instance, and generally follow this logic invoker = refprotocol.refer(interfaceClass, urls.get(0)); }
// Multiple registries, multiple service providers, or a mix of both else { List<Invoker<?>> invokers = new ArrayList<Invoker<?>>(); URL registryURL = null; for (URL url : urls) { // Get all Invokers and call refer to build Invoker through refprotocol, which runs // Load the specified Protocol instance according to the url protocol header and call the refer method of the instance invokers.add(refprotocol.refer(interfaceClass, url)); if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { registryURL = url; } } // If the registry link is not empty, Available Cluster will be used if (registryURL != null) { URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME); // Create a StaticDirectory instance and merge multiple Invoker s by Cluster invoker = cluster.join(new StaticDirectory(u, invokers)); } else { invoker = cluster.join(new StaticDirectory(invokers)); } } }
Boolean c = check; if (c == null && consumer != null) { c = consumer.isCheck(); } if (c == null) { // default true c = true; } // invoker availability check if (c && !invoker.isAvailable()) { // Throwing anomaly } // 2. Generating proxy classes, core // After Invoker is created, the next thing to do is to generate proxy objects for service interfaces. With proxy objects, // Remote calls can be made. The entry method generated by proxy objects is ProxyFactory's getProxy. return (T) proxyFactory.getProxy(invoker); }
3.2.2. Create Invoker
// Following is an analysis of creating Invoker, a single registry or service provider (direct service connection, the same below) if (urls.size() == 1) { // 1. Call Registry Protocol's refer to construct an Invoker instance, and generally follow this logic invoker = refprotocol.refer(interfaceClass, urls.get(0)); }
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { // protocol for zookeeper, debugging plus String protocol = url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY); url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY); // Get an instance of a registry Registry registry = registryFactory.getRegistry(url); // Here the type is "" if (RegistryService.class.equals(type)) { return proxyFactory.getInvoker((T) registry, type, url); }
// Convert url query string to Map Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY)); // Get the group configuration String group = qs.get(Constants.GROUP_KEY); if (group != null && group.length() > 0) { if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) { // Load Mergeable Cluster instance through SPI and call doRefer to continue executing service reference logic return doRefer(getMergeableCluster(), registry, type, url); } } // Call doRefer to continue executing the service reference logic return doRefer(cluster, registry, type, url); }