First, let's take a look at the implementation classes of directory interface. There are two main implementation classes: StaticDirectory and RegistryDirectory. This paper mainly analyzes RegistryDirectory.
StaticDirectory
From the Static keywords in the StaticDirectory, we can see that this will not change dynamically. From the figure below, his Invoker is passed in through the constructor, and the StaticDirectory is used less, mainly for the reference of services to multiple registries
RegistryDirectory
First look at its structure:
The notify method in the NotifyListener is the callback of the registry, which is the reason why it can dynamically change according to the registry
Above, the doList method of Directory, which is an abstract method, selects the invoker list from the Directory by calling the doList method of the subclass RegistryDirectory.
@Override public List<Invoker<T>> doList(Invocation invocation) { if (forbidden) { // If forbidden is true, no service provider or service provider is unavailable throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist)."); } List<Invoker<T>> invokers = null; Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) { // Get method name String methodName = RpcUtils.getMethodName(invocation); // Get method parameters Object[] args = RpcUtils.getArguments(invocation); if (args != null && args.length > 0 && args[0] != null && (args[0] instanceof String || args[0].getClass().isEnum())) { // If the first parameter is a string type or an enumeration type // You can enumerate routes according to the first parameter invokers = localMethodInvokerMap.get(methodName + "." + args[0]); } if (invokers == null) { // Still null, get with method name invokers = localMethodInvokerMap.get(methodName); } if (invokers == null) { // Let it still be null, use * to randomly select an invoker to implement the call invokers = localMethodInvokerMap.get(Constants.ANY_VALUE); } if (invokers == null) { // Still null, use iterator to get an invoker Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator(); if (iterator.hasNext()) { invokers = iterator.next(); } } } return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers; }
It can be seen from this that the Directory obtains the invoker from the methodInvokerMap. When does the invoker write to the methodInvokerMap in the Directory? It operates when the method notify is called back.
@Override public synchronized void notify(List<URL> urls) { // URL reference array for declaring invoker List<URL> invokerUrls = new ArrayList<URL>(); // Declare the URL reference array of router List<URL> routerUrls = new ArrayList<URL>(); // Declare the URL reference array of the configurator List<URL> configuratorUrls = new ArrayList<URL>(); for (URL url : urls) { // Get agreement name String protocol = url.getProtocol(); // Acquisition classification String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); if (Constants.ROUTERS_CATEGORY.equals(category) || Constants.ROUTE_PROTOCOL.equals(protocol)) { // If it is route classification or routing protocol routerUrls.add(url); } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) || Constants.OVERRIDE_PROTOCOL.equals(protocol)) { // If it's configurator classification or protocol rewriting configuratorUrls.add(url); } else if (Constants.PROVIDERS_CATEGORY.equals(category)) { // If provider classification invokerUrls.add(url); } else { logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()); } } // Get configurators through url if (configuratorUrls != null && !configuratorUrls.isEmpty()) { this.configurators = toConfigurators(configuratorUrls); } // Get routes through url if (routerUrls != null && !routerUrls.isEmpty()) { List<Router> routers = toRouters(routerUrls); if (routers != null) { // null - do nothing setRouters(routers); } } List<Configurator> localConfigurators = this.configurators; // Merge override parameters this.overrideDirectoryUrl = directoryUrl; if (localConfigurators != null && !localConfigurators.isEmpty()) { for (Configurator configurator : localConfigurators) { this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl); } } // Refresh providers refreshInvoker(invokerUrls); } /** * Convert the invokerUrl list to the invoker Map. The conversion rules are as follows * 1.If the URL has been converted to an invoker, it is no longer re referenced, and it is obtained directly from the cache, and note that any parameter changes in the URL will be re referenced. * 2.If the incoming invoker list is not empty, it means it is the latest call list. * 3.If the incoming invokerUrl list is empty, it means that the rule is only an override or routing rule, which needs to be re compared to determine whether to re reference. * @param invokerUrls this parameter can't be null */ private void refreshInvoker(List<URL> invokerUrls) { if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { // There is only one invoker url, and the protocol is empty // Set no access this.forbidden = true; // Set methodInvokerMap to null this.methodInvokerMap = null; // Close all invoker s destroyAllInvokers(); } else { // Set allow access this.forbidden = false; Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) { // If the incoming invoker url is empty, add it from the cache invokerUrls.addAll(this.cachedInvokerUrls); } else { // Cache the invoker url for comparison this.cachedInvokerUrls = new HashSet<URL>(); this.cachedInvokerUrls.addAll(invokerUrls); } if (invokerUrls.isEmpty()) { return; } // Convert the invoker url list to a Map with key as url and value as invoker Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls); // Convert the invoker url list to a Map with key as method and value as invoker Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) { logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString())); return; } // If there are multiple groups, merge methodInvokerMap this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap; this.urlInvokerMap = newUrlInvokerMap; try { // Close without invoker destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); } catch (Exception e) { logger.warn("destroyUnusedInvokers error. ", e); } } }
In other words, if the registry changes, update the values of methodInvokerMap and urlInvokerMap (which will be mentioned later when we talk about the service reference principle). This is why the values mentioned on the official website may be dynamic, such as the reason why the registry pushes changes