In the previous article we covered Dubbo custom tag parsing, where our custom XML tag < Dubbo :service /> is parsed as a ServiceBean object (portal: Dubbo XML configuration load). Today we’re going to be talking about ServiceBean! A careful reader reading the ServiceBean class will notice that the onApplicationEvent() and afterPropertiesSet() methods call a common method export(). Our intuition tells us that this method should be related to service exposure, so we will start with the export() method.

Export () method call timing

To answer the export() call timing question, we need to focus on three methods in the ServiceBean class

  1. The setApplicationContext ApplicationContext ServiceBean implements the ApplicationContextAware interface, After ServiceBean is initialized, setApplicationContext is called to inject the Spring context;
  2. AfterPropertiesSet () injects ApplicationConfig, registries, protocols, and other properties;
  3. OnApplicationEvent (ContextRefreshedEvent Event) The event type accepted here is ContextRefreshedEvent. This method is called when applicationContext is initialized or refreshed. The three methods are called in roughly the order shown in the Spring lifecycle setApplicationContext() — > afterPropertiesSet() — > onApplicationEvent()
public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
    SpringExtensionFactory.addApplicationContext(applicationContext);
    supportedApplicationListener = addApplicationListener(applicationContext, this);
}

public void onApplicationEvent(ContextRefreshedEvent event) {
    if(! isExported() && ! isUnexported()) {if (logger.isInfoEnabled()) {
    		logger.info("The service ready on spring started. service: "+ getInterface()); } export(); }}public void afterPropertiesSet(a) throws Exception {
    / / to omit...
    if (!supportedApplicationListener) {
    	export();
    }
}
Copy the code

The code execution logic is roughly as follows:

  1. The setApplicationContext() method is first executed to inject the context. Here supportedApplicationListener Spring monitoring mechanism is used to determine whether the Spring support.
  2. Execute the afterPropertiesSet() method. If the supportedApplicationListener value to false, calling export () method.
  3. Execute the onApplicationEvent() method. Call the export() method if the export() and unexport() methods have not been executed. From the simple analysis above we can see that the export() method is called only once in the onApplicationEvent() and export() methods.

Export () method parsing

public synchronized void export() {
	if(provider ! = null) {if (export == null) {
			export = provider.getExport();
		}
		if(delay == null) { delay = provider.getDelay(); }}if (export! = null && !export) {
		return;
	}

	if(delay ! = null && delay > 0) { delayExportExecutor.schedule(newRunnable() {
			@Override
			public void run() {
				doExport();
			}
		}, delay, TimeUnit.MILLISECONDS);
	} else {
		doExport(); }}Copy the code

The export() method is simpler. Notice that there is a delay variable that we can use to delay the execution of the export() method. Continue with the doExport() method

protected synchronized void doExport(a) {
	/ / to omit...
	doExportUrls();
	ProviderModel providerModel = newProviderModel(getUniqueServiceName(), ref, interfaceClass); ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel); }private void doExportUrls(a) {
	List<URL> registryURLs = loadRegistries(true);
	for(ProtocolConfig protocolConfig : protocols) { doExportUrlsFor1Protocol(protocolConfig, registryURLs); }}Copy the code

The doExport() method omits much of the ServiceBean configuration checksum initialization code. If you are interested, you can read it for yourself. Here is the point!! Analyze doExportUrls() methods!! First look at the loadRegistries() method:

loadRegistries()

protected List<URL> loadRegistries(boolean provider) {
	checkRegistry();
	List<URL> registryList = new ArrayList<URL>();
	// Registries are initialized in afterPropertiesSet()
	if(registries ! =null && !registries.isEmpty()) {
		for (RegistryConfig config : registries) {
			String address = config.getAddress();
			if (address == null || address.length() == 0) {
				address = Constants.ANYHOST_VALUE;
			}
			String sysaddress = System.getProperty("dubbo.registry.address");
			if(sysaddress ! =null && sysaddress.length() > 0) {
				address = sysaddress;
			}
			if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
				Map<String, String> map = new HashMap<String, String>();
				// Integrate application/config attributes into map. See:
				appendParameters(map, application);
				appendParameters(map, config);
				map.put("path", RegistryService.class.getName());
				map.put("dubbo", Version.getProtocolVersion());
				map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
				if (ConfigUtils.getPid() > 0) {
					map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
				}
				if(! map.containsKey("protocol")) {
					if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
						map.put("protocol"."remote");
					} else {
						map.put("protocol"."dubbo"); }}/ / build the url, return a result similar to zookeeper: / / 192.168.0.100:2181 / org. Apache. Dubbo. Registry. RegistryService?
				/ / application = demo - provider&dubbo = 2.0.2 & pid = 22705 & qos. Port = 22222 & timestamp = 1549005672530
				List<URL> urls = UrlUtils.parseURLs(address, map);
				for (URL url : urls) {
				    // Save the protocol of this URL to the registry parameter
					url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
					// Set the URL protcol property to registry
					url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
					if ((provider && url.getParameter(Constants.REGISTER_KEY, true) | | (! provider && url.getParameter(Constants.SUBSCRIBE_KEY,true))) {
						registryList.add(url);
					}
				}
			}
		}
	}
	return registryList;
}
Copy the code

LoadRegistries () is used to load the registry. In a nutshell, this is used to resolve the
tag that we define in the configuration file. The checkRegistry() method is used to validate the registry configuration checksum, which contains some version-compatible code. See the appendParameters() section for the appendParameters() method.

Local exposure

After the loadRegistries() method is introduced, we move on to doExportUrlsFor1Protocol(). The doExportUrlsFor1Protocol() method is relatively long, here we select content relevant to local exposure for analysis.

if(! Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {// export to local if the config is not remote (export to remote only when config is remote)
    if(! Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) { exportLocal(url); }if(! Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {// Remote expose related content, omit...}}private void exportLocal(URL url) {
    if(! Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { URL local = URL.valueOf(url.toFullString()) .setProtocol(Constants.LOCAL_PROTOCOL) .setHost(LOCALHOST) .setPort(0); Exporter<? > exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass, local)); exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry"); }}Copy the code

Seeing the exportLocal() method means that we are getting to the core of local service exposure! Even more unbearable is! Again, the SPI mechanism in Dubbo is used (see Dubbo SPI in the first article of the series). Let’s see what’s going on here?

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
Copy the code

Familiar formula familiar material, here we get Protocol and ProxyFactory corresponding adaptive extension class. Based on the nested logic of method calls, let’s look at the getInvoker() method of ProxyFactory Adaptive extension class ProxyFactory$Adaptive.

The core method proxyFactory.getInvoker()

public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
    public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy"."javassist");
        if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        org.apache.dubbo.rpc.ProxyFactory extension = null;
        try{ extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtens ion(extName); }catch(Exception e){
            if (count.incrementAndGet() == 1) {
                logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.ProxyFactory, will use default extension javassist instead.", e); } extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtens ion("javassist");
        }
        returnextension.getInvoker(arg0, arg1, arg2); }}Copy the code

We’ll actually call the getInvoker() method of the StubProxyFactoryWrapper class.

public class StubProxyFactoryWrapper implements ProxyFactory {
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException {
        returnproxyFactory.getInvoker(proxy, type, url); }}public class JavassistProxyFactory extends AbstractProxyFactory {
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, Class
       [] parameterTypes, Object[] arguments) throws Throwable {
                returnwrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); }}; }}Copy the code

The JavassistProxyFactory getInvoker() method is called. The Wrapper is a dynamically generated proxy object. Finally, an AbstractProxyInvoker object is returned. The doInvoke() method calls the invokeMethod() method of the Wrapper proxy class, which looks something like this:

public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {
    org.apache.dubbo.demo.provider.DemoServiceImpl w;
    try {
        w = ((org.apache.dubbo.demo.provider.DemoServiceImpl) $1);
    } catch (Throwable e) {
        throw new IllegalArgumentException(e);
    }
    try {
        if ("sayHello".equals($2) && $3.length == 1) {
            return ($w) w.sayHello((java.lang.String) $4[0]); }}catch (Throwable e) {
        throw new java.lang.reflect.InvocationTargetException(e);
    }
    throw new org.apache.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" + $2 + "\" in class org.apache.dubbo.demo.provider.DemoServiceImpl.");
}
Copy the code

At least now that we’ve looked at the proxyFactory.getInvoker() method, we’ve got an AbstractProxyInvoker object that wraps a dynamic proxy class. Move on to the protocol.export() method.

The core method protocol.export()

public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
    if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
    if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = ( url.getProtocol() ==null ? "dubbo" : url.getProtocol() );
    if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
    org.apache.dubbo.rpc.Protocol extension = null;
    try{ extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extN ame); }catch(Exception e){
        if (count.incrementAndGet() == 1) {
            logger.warn("Failed to find extension named " + extName + " for type org.apache.dubbo.rpc.Protocol, will use default extension dubbo instead.", e);
        }
        extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension("dubbo");
    }
    return extension.export(arg0);
}
Copy the code

The value of protocol in the URL is injvm(protocol is updated to injvm after the URL setProtocol(LOCAL_PROTOCOL) operation). So the extension we get here is actually a wrapper object that wraps InjvmProtocol. For those who have questions about the Wrapper class, see the Dubbo SPI mechanism. There is a method involved, buildInvokerChain(), which builds a chain of calls. The overall call sequence diagram is as follows:

InjvmExporter

buildInvokerChain()

ProtocolFilterWrapper.java
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
	Invoker<T> last = invoker;
	List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
	if(! filters.isEmpty()) {for (int i = filters.size() - 1; i >= 0; i--) {
			final Filter filter = filters.get(i);
			final Invoker<T> next = last;
			last = new Invoker<T>() {
				// omit the Invoker build code...
				@Override
				public Result invoke(Invocation invocation) throws RpcException {
					return filter.invoke(next, invocation);
				}
				// omit the Invoker build code...}; }}return last;
}
Copy the code

The buildInvokerChain() method is used to build the call chain, which at first glance should be made up of the Filter extension class. So where do these Filter extensions come from? This line of code is critical!!

List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
Copy the code

There should be a strong familiarity with this code, but a closer look is slightly different. In fact, the extension classes marked with the @Activate annotation are loaded into the cachedAttributes collection of the ExtensionLoader class. When we call getActivateExtension() of ExtensionLoader, filter objects that meet the current condition are obtained from the CachedAttributes collection based on the key and group values we passed in. Once the collection of filters is retrieved, the chain is concatenated as a linked list of filters. For example, suppose that the filter objects in the collection are filter0, filter1, and filter2. We traverse the set of filters in reverse order. Last is actually the newly created IVK2 object. If we call last’s invoke method, the invocation chain looks like this:

End

This article describes when the Export() method is called and the basic flow. Some section is devoted to the analysis of Dubbo service local exposure. There’s a lot of code analysis involved, maybe not everything. Or suggest you Debug yourself, a lot of things in seconds to understand, help to understand the source code. In the next article we introduce the Dubbo service remote exposure.

appendProperties()

protected static void appendProperties(AbstractConfig config) {
	if (config == null) {
		return;
	}
	// getTagName: get the lower case class name with the Bean/Config end removed (ApplicationConfig->application)
	String prefix = "dubbo." + getTagName(config.getClass()) + ".";
	Method[] methods = config.getClass().getMethods();
	for (Method method : methods) {
		try {
			String name = method.getName();
			// the method length is greater than 3; 2. The method starts with set. 3. The method modifier type is public. 4. The number of parameters is 1. 5. The parameter type is basic
			if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(method.getModifiers())
					&& method.getParameterTypes().length == 1 && isPrimitive(method.getParameterTypes()[0]) {// camelToSplitName: for example ApplicationConfig -- >application.config
				String property = StringUtils.camelToSplitName(name.substring(3.4).toLowerCase() + name.substring(4), ".");

				String value = null;
				if(config.getId() ! =null && config.getId().length() > 0) {
					// Concatenate the attribute name and try to get the corresponding attribute
					String pn = prefix + config.getId() + "." + property;
					value = System.getProperty(pn);
					if(! StringUtils.isBlank(value)) { logger.info("Use System Property " + pn + " to config dubbo"); }}if (value == null || value.length() == 0) {
					// For example, the current config is ApplicationConfig, pn = dubo.application. XXX
					String pn = prefix + property;
					value = System.getProperty(pn);
					if(! StringUtils.isBlank(value)) { logger.info("Use System Property " + pn + " to config dubbo"); }}if (value == null || value.length() == 0) {
					Method getter;
					try {
						getter = config.getClass().getMethod("get" + name.substring(3));
					} catch (NoSuchMethodException e) {
						try {
							getter = config.getClass().getMethod("is" + name.substring(3));
						} catch (NoSuchMethodException e2) {
							getter = null; }}if(getter ! =null) {
						if (getter.invoke(config) == null) {
							// Try using the configUtils.getProperty () method to get the property value
							// Try to read the properties from either dubo.properties. file or dubo.properties
							if(config.getId() ! =null && config.getId().length() > 0) {
								value = ConfigUtils.getProperty(prefix + config.getId() + "." + property);
							}
							if (value == null || value.length() == 0) {
								value = ConfigUtils.getProperty(prefix + property);
							}
							if (value == null || value.length() == 0) {
								String legacyKey = legacyProperties.get(prefix + property);
								if(legacyKey ! =null && legacyKey.length() > 0) {
									value = convertLegacyValue(legacyKey, ConfigUtils.getProperty(legacyKey));
								}
							}

						}
					}
				}
				if(value ! =null && value.length() > 0) {
					method.invoke(config, convertPrimitive(method.getParameterTypes()[0], value)); }}}catch(Exception e) { logger.error(e.getMessage(), e); }}}Copy the code

appendParameters()

protected static void appendParameters(Map<String, String> parameters, Object config) {
	appendParameters(parameters, config, null);
}
protected static void appendParameters(Map<String, String> parameters, Object config, String prefix) {
	if (config == null) {
		return;
	}
	Method[] methods = config.getClass().getMethods();
	// Iterate through the config class method collection
	for (Method method : methods) {
		try {
			String name = method.getName();
			// find a method that starts with set/is, not getClass; The method modifier is public; The number of method parameters is 0; The return type is basic
			if ((name.startsWith("get") || name.startsWith("is")) &&!"getClass".equals(name)
					&& Modifier.isPublic(method.getModifiers())
					&& method.getParameterTypes().length == 0
					&& isPrimitive(method.getReturnType())) {
				// Get the parameter annotation
				Parameter parameter = method.getAnnotation(Parameter.class);
				// @parameter (excluded = true), skip it
				if(method.getReturnType() == Object.class || parameter ! =null && parameter.excluded()) {
					continue;
				}
				int i = name.startsWith("get")?3 : 2;
				String prop = StringUtils.camelToSplitName(name.substring(i, i + 1).toLowerCase() + name.substring(i + 1), ".");
				String key;
				if(parameter ! =null && parameter.key().length() > 0) {
					key = parameter.key();
				} else {
					key = prop;
				}
				// Call the get/is method of the config class with reflection
				Object value = method.invoke(config);
				String str = String.valueOf(value).trim();
				if(value ! =null && str.length() > 0) {
					// Whether to escape, utF-8
					if(parameter ! =null && parameter.escaped()) {
						str = URL.encode(str);
					}
					if(parameter ! =null && parameter.append()) {
						String pre = parameters.get(Constants.DEFAULT_KEY + "." + key);
						if(pre ! =null && pre.length() > 0) {
							str = pre + "," + str;
						}
						pre = parameters.get(key);
						if(pre ! =null && pre.length() > 0) {
							str = pre + ","+ str; }}if(prefix ! =null && prefix.length() > 0) {
						key = prefix + "." + key;
					}
					// key/value adds to the parameters collection
					parameters.put(key, str);
				} else if(parameter ! =null && parameter.required()) {
					throw new IllegalStateException(config.getClass().getSimpleName() + "." + key + " == null");
				}
				// The method is called getParameters(); The method modifier is public; Method parameter number is 0; The return type is Map
			} else if ("getParameters".equals(name)
					&& Modifier.isPublic(method.getModifiers())
					&& method.getParameterTypes().length == 0
					&& method.getReturnType() == Map.class) {
				Map<String, String> map = (Map<String, String>) method.invoke(config, new Object[0]);
				if(map ! =null && map.size() > 0) { String pre = (prefix ! =null && prefix.length() > 0 ? prefix + "." : "");
					for (Map.Entry<String, String> entry : map.entrySet()) {
						parameters.put(pre + entry.getKey().replace(The '-'.'. '), entry.getValue()); }}}}catch (Exception e) {
			throw newIllegalStateException(e.getMessage(), e); }}}Copy the code

This method calls the isXXX/getXXX method of the current class object (not the getClass method; The method modifier is public; The number of parameters is 0; The return type is basic), gets its return value to construct a key-value pair to add to the specified map collection; The results returned by getParameters() are also parsed to construct key-value pairs that are injected into the Map collection.

Original articles on this BLOG are not allowed to be used for commercial purposes and traditional media without my permission. Network media reprint please indicate the source, otherwise belongs to infringement. https://juejin.cn/post/6844903751162003464