preface

In previous articles, we looked in depth at Java’s SPI mechanism, which is a service discovery mechanism. For details, see: In-depth understanding of the JDK’s SPI mechanism

Before going any further into Dubbo, we must first understand the SPI mechanism in Dubbo. Because a great god (anonymous) once said:

To understand Dubbo, one must first understand the Dubbo SPI mechanism, otherwise it will be very confusing.

The background,

1, the source

Dubbo’s extension point loading is enhanced from the JDK standard Service Provider Interface (SPI) extension point discovery mechanism. However, it also improves the following issues with the JDK standard SPI:

  • The JDK’s standard SPI instantiates all implementations of extension points at once, which is time-consuming to initialize if there is an extension implementation, but wasteful of resources to load without it.

  • If the extension point fails to load, you will not even get the name of the extension point. Such as: If the RubyScriptEngine class fails to load because the jruby.jar on which RubyScriptEngine depends does not exist, the class fails to load because the jruby.jar on which RubyScriptEngine depends does not exist. This failure cause is eaten up and does not correspond to Ruby. When a user executes a Ruby script, it will report that Ruby is not supported, rather than the actual failure cause.

  • Added support for extension points IoC and AOP, where one extension point can directly setter for injection of other extension points.

2, agreed

In the jar package of the extension class, place the extension point configuration file meta-INF /dubbo/ fully qualified name of the interface. The content is: Configuration name = fully qualified name of the extension implementation class. Multiple implementation classes are separated by newlines.

3. Configuration files

The configuration files required for Dubbo SPI are placed in the meta-INF/Dubbo path, and almost all functions are implemented with extension points.

Let’s take the Protocol interface, which has many implementations.

Second, the Dubbo SPI

Dubbo SPI can be configured as key-value pairs, so that the specified implementation classes can be loaded on demand. The logic of the Dubbo SPI is encapsulated in the ExtensionLoader class, through which we can load the specified implementation class. Each extension interface corresponds to an ExtensionLoader object, which is affectionately referred to here as an extension point loader.

Let’s take a look at its properties:

Public class ExtensionLoader<T> {// Path to the extension point configuration file. You can load the extension point configuration file from three places. Private static final String SERVICES_DIRECTORY ="META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/"; Private static final ConcurrentMap<Class<? >, ExtensionLoader<? >> EXTENSION_LOADERS = new ConcurrentHashMap<Class<? >, ExtensionLoader<? > > (); Private static final ConcurrentMap<Class<? >, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<? >,Object>(); Private final ConcurrentMap<Class<? >, String> cachedNames = new ConcurrentHashMap<Class<? >, String>(); Private final Holder<Map<String, Class<? >>> cachedClasses = new Holder<Map<String, Class<? > > > (); Private final Map<String, Activate> cachedMap = new ConcurrentHashMap<String, Activate>(); Private Final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>(); }Copy the code

ExtensionLoader caches different extension point configurations and implementations. Dubbo also gives us a reminder on the website that extension points are loaded using a single instance (make sure the extension implementation is thread-safe) and cached in the ExtensionLoader. Let’s look at a few key approaches.

1. Get the extension point loader

We first by ExtensionLoader. GetExtensionLoader () method to obtain a ExtensionLoader instance, it is the extension point loader. The extension class object is then obtained through the getExtension method of the ExtensionLoader. In this case, the getExtensionLoader method is used to get the ExtensionLoader corresponding to the extended class from the cache, or create a new instance if the cache misses.

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null)
    	throw new IllegalArgumentException("Extension type == null");
    if(! type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    if(! withExtensionAnnotation(type)) {
    	throw new IllegalArgumentException("Extension type("."); } ExtensionLoader
      
        loader = (ExtensionLoader
       
        ) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader
        
         (type)); loader = (ExtensionLoader
         
          ) EXTENSION_LOADERS.get(type); } return loader; }
         
        
       
      Copy the code

For example, you can get an ExtensionLoader instance of the Protocol interface like this: ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class); Can get extension point of the loader object instance: com.alibaba.dubbo.com mon. The extension. ExtensionLoader [. Com. Alibaba. Dubbo. RPC Protocol]

2. Get the extension class object

We got the loader in the previous step, and then we can get the extension class object by the name of the extension point, based on the loader instance.

Public T getExtension(String name) {// Check the validity of the extension point nameif (name == null || name.length() == 0)
    	throw new IllegalArgumentException("Extension name == null"); // Get the default extension implementation classif ("true".equals(name)) {
    	returngetDefaultExtension(); } Holder<Object> Holder = cachedInstances. Get (name);if (holder == null) {
    	cachedInstances.putIfAbsent(name, new Holder<Object>());
    	holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    if (instance == null) {
    	synchronized (holder) {
    	    instance = holder.get();
    	    if(instance == null) { instance = createExtension(name); holder.set(instance); }}}return (T) instance;
}
Copy the code

It tries to fetch it from the cache and creates an extension object if it misses. So what was the creation process like?

Private T createExtension(String name) {private T createExtension(String name) {private T createExtension(String name) { > clazz = getExtensionClasses().get(name);if(clazz == null) { throw findException(name); } try {// create an instance by reflection and then put it into cache T instance = (T) EXTENSION_INSTANCES. Get (clazz);if(instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // Inject dependency injectExtension(instance); Set<Class<? >> wrapperClasses = cachedWrapperClasses;if(wrapperClasses ! = null && ! Wrapperclasses.isempty ()) {// wrap as a Wrapper instancefor(Class<? > wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); }}return instance;
    } catch (Throwable t) {
    	throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
    	    type + ") could not be instantiated: "+ t.getMessage(), t); }}Copy the code

The focus here is on dependency injection and Wrapper Wrapper classes, which are concrete implementations of IOC and AOP in Dubbo.

2.1. Dependency injection

Inject a dependency into the extended object, which gets all the methods of the class. If the method starts with set, has only one argument, and the method access level is public, the property value is set by reflection. Therefore, IOC in Dubbo supports only setter injection.

private T injectExtension(T instance) {
	
    if(objectFactory ! = null) {for (Method method : instance.getClass().getMethods()) {
    	    if (method.getName().startsWith("set")&& method.getParameterTypes().length == 1&& Modifier.isPublic(method.getModifiers())) { Class<? > pt = method.getParameterTypes()[0]; try { String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) :"";
    		    	Object object = objectFactory.getExtension(pt, property);
    		    	if(object ! = null) { method.invoke(instance, object); } } catch (Exception e) { logger.error(""); }}}}return instance;
}
Copy the code
2.2, the Wrapper

It passes the current instance as an argument to the Wrapper constructor and creates the Wrapper instance through reflection. We then inject dependencies into the Wrapper instance, and finally assign the Wrapper instance to the instance variable again. It might be a little tricky to say, but let’s just look at the object it generates at the end. Let’s take DubboProtocol as an example. It wraps objects like:

To sum up, if we get an extension class object, we end up with an instance of the Wrapper class. Something like this:

ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol extension = extensionLoader.getExtension("dubbo");
System.out.println(extension);
Copy the code

Output: com. Alibaba. Dubbo.. RPC protocol. 4 cdf35a9 ProtocolListenerWrapper @

3. Get all extension classes

Before we can get the extension class object by name, we first need to resolve all the extension classes according to the configuration file. It is a mapping of extension point names and extension classes Map

>
,>

First, we’ll fetch it from the cache again, and if not, we’ll call loadExtensionClasses to load it from the configuration file. The configuration file has three paths:

META-INF/services/ META-INF/dubbo/ META-INF/dubbo/internal/

Try to get it from the cache first.

private Map<String, Class<? >>getExtensionClasses() {// Get Map<String, Class<? >> classes = cachedClasses.get();if (classes == null) {
    	synchronized (cachedClasses) {
    	    classes = cachedClasses.get();
    	    if(classes == null) {// loadExtensionClasses = loadExtensionClasses(); cachedClasses.set(classes); }}}return classes;
}	
Copy the code

If not, call loadExtensionClasses to read from the configuration file.

private Map<String, Class<? >>loadExtensionClasses() {// Get the SPI annotation, heretypeThe variable is the final SPI defaultAnnotation = type.getannotation (spi.class) passed in when the getExtensionLoader method is called;if(defaultAnnotation ! = null) { String value = defaultAnnotation.value();if ((value = value.trim()).length() > 0) {
			String[] names = NAME_SEPARATOR.split(value);
			if (names.length > 1) {
				throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()+ ":"+ Arrays.toString(names)); } // Set the default extension name, refer to the getDefaultExtension method // if the name istrue, is to call the default uplike classif(names.length == 1) cachedDefaultName = names[0]; }} // Load the configuration file of the specified path Map<String, Class<? >> extensionClasses = new HashMap<String, Class<? > > (); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadDirectory(extensionClasses, DUBBO_DIRECTORY); loadDirectory(extensionClasses, SERVICES_DIRECTORY);return extensionClasses;
}
Copy the code

In the case of the Protocol interface, we get the following set of implementation classes, so we can load concrete extension class objects by name.

{
	registry=class com.alibaba.dubbo.registry.integration.RegistryProtocol
	injvm=class com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
	thrift=class com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
	mock=class com.alibaba.dubbo.rpc.support.MockProtocol
	dubbo=class com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
	http=class com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
	redis=class com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol
	rmi=class com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
}
Copy the code

Adaptive extension mechanism

In Dubbo, many extensions are loaded through the SPI mechanism, such as Protocol, Cluster, LoadBalance, etc. Instead of being loaded at framework startup, these extensions are loaded based on URL object parameters when extension methods are called. Dubbo, then, addresses this problem with an adaptive scaling mechanism.

The implementation logic for the adaptive extension mechanism is as follows: First Dubbo generates proxy code for the extension interface. You then compile this code with Javassist or JDK to get the Class Class. Finally, reflection is used to create a proxy class in which the parameters of the URL object are used to determine which implementation class to call.

1. Adaptive annotations

Before we begin, it is important to take a look at the Adaptive annotation, which is closely related to Adaptive scaling.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}
Copy the code

As you can see from the above code, Adaptive can be annotated on classes or methods.

  • Annotation on a class Dubbo does not generate a proxy class for that class.
  • Annotation on the method Dubbo generates proxy logic for the method, indicating that the current method needs to be implemented by invoking the corresponding extension point based on the parameter URL.

2. Access to adaptive extension classes

The getAdaptiveExtension method is the entry method to getAdaptiveExtension.

public T getAdaptiveExtension() {/ / from the cache for adaptive development Object instance = cachedAdaptiveInstance. The get ();if (instance == null) {
    	if(createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get();  // If the cache is not hit, create an adaptive extension and put it in the cacheif (instance == null) {
    		    	try {
    		    	    instance = createAdaptiveExtension();
    		    	    cachedAdaptiveInstance.set(instance);
    		    	} catch (Throwable t) {
    		    	    createAdaptiveInstanceError = t;
    		    	    throw new IllegalStateException("", t);
    		    	}
    		    }
    		}
    	}
    }
    return (T) instance;
}
Copy the code

The getAdaptiveExtension method first checks the cache. If the cache does not hit, the createAdaptiveExtension method is called to create the adaptive extension.

private T createAdaptiveExtension() {
    try {
    	return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
    	throw new IllegalStateException("Can not create adaptive extension ....", e); }}Copy the code

The code is less, call getAdaptiveExtensionClass method for adaptive development Class object, and through reflection instantiation, the last call injectExtension method to expand the instance of injection. The process of obtaining the adaptive extension class is as follows:

private Class<? >getAdaptiveExtensionClassCachedAdaptiveClass is not null getExtensionClasses();if(cachedAdaptiveClass ! = null) {returncachedAdaptiveClass; } // If the above conditions are not true, create an adaptive extension classreturn cachedAdaptiveClass = createAdaptiveExtensionClass();
}
Copy the code

In the above method, it first gets all the implementation classes of the current interface, and if an implementation class annotates @Adaptive, that class is assigned to the cachedAdaptiveClass variable and returned. If not, call createAdaptiveExtensionClass create adaptive development classes.

3. Create adaptive extension classes

CreateAdaptiveExtensionClass method is used to generate the adaptive development class, this method first generates the adaptive to expand the source of class and then by the Compiler instance (default using javassist Dubbo as a Compiler) to compile the source code, Get the proxy Class instance.

private Class<? >createAdaptiveExtensionClass() {/ / build adaptive development code String code = createAdaptiveExtensionClassCode (); ClassLoader classLoader = findClassLoader(); The default Dubbo is javassist Compiler Compiler =ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension(); // Compile the code to return the object of the class instancereturn compiler.compile(code, classLoader);
}
Copy the code

Before generating the Adaptive extension class, Dubbo checks if the interface method contains @adaptive. If none of the methods have this annotation, throw an exception.

if(! hasAdaptiveAnnotation){ throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
}
Copy the code

Again, take the Protocol interface as an example, whose export() and refer() methods are labeled @adaptive. Destroy and getDefaultPort are not annotated with @Adaptive. Dubbo does not generate proxy logic for methods that do not have Adaptive annotations; for this type of method, only a line of code that throws an exception is generated.

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;

@SPI("dubbo")
public interface Protocol {
    int getDefaultPort();
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
    void destroy();
}
Copy the code

So when we call these two methods, we will first get the protocol name in the URL object, and then find the specific extension point implementation class according to the name, and then call. Here is the source code for generating an instance of the adaptive extension class:

package com.viewscenes.netsupervisor.adaptive;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Protocol;
import com.alibaba.dubbo.rpc.RpcException;

public class Protocol$Adaptive implements Protocol {

    public void destroy() {
    	throw new UnsupportedOperationException("...");
    }
    public int getDefaultPort() {
    	throw new UnsupportedOperationException("...");
    }
    public Exporter export(Invoker invoker)throws RpcException {
    	if (invoker == null) {
    		throw new IllegalArgumentException("Invoker argument == null");
    	}
    	if (invoker.getUrl() == null) {
    		throw new IllegalArgumentException("Invoker argument getUrl() == null");
    	}
    		
    	URL url = invoker.getUrl();
    	String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
    	if (extName == null) {
    		throw new IllegalStateException("...");
    	}
    		
    	Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    	return extension.export(invoker);
    }
    public Invoker refer(Class clazz,URL ur)throws RpcException {
    	if (ur == null) {
    		throw new IllegalArgumentException("url == null");
    	}
    	URL url = ur;
    	String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
    	if (extName == null) {
    		throw new IllegalStateException("...");
    	}
    	Protocol extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
    	returnextension.refer(clazz, url); }}Copy the code

To sum up, when we acquire an Adaptive extension class of an interface, it is actually an instance of Adaptive class.

ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);            
Protocol adaptiveExtension = extensionLoader.getAdaptiveExtension();
System.out.println(adaptiveExtension);
Copy the code

Output: com. Alibaba. Dubbo.. RPC Protocol $47 f6473 Adaptive @

Four, the instance,

After looking at the process above, it is easy to write your own logic to replace the process in Dubbo.

Dubbo exposes the service using the Dubbo protocol by default. We can replace it with a custom protocol.

1. Implementation classes

First, we create a MyProtocol class that implements the Protocol interface.

package com.viewscenes.netsupervisor.protocol;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.rpc.Exporter;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Protocol;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol;

public class MyProtocol extends DubboProtocol implements Protocol{

	public int getDefaultPort() {
		return 28080;
	}
	public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {	
		URL url = invoker.getUrl();
		System.out.println("Custom protocol for service exposure :"+url);	
		return super.export(invoker);
	}
	public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
		return super.refer(type, url);
	}
	public void destroy() {}}Copy the code

2. Extension point configuration file

Then, in your own project meta-inf/services create com. Alibaba. Dubbo.. RPC Protocol file, the file content as follows: myProtocol=com.viewscenes.netsupervisor.protocol.MyProtocol

3. Modify the Dubbo configuration file

Finally modify the configuration file on the producer side:

<! -- Expose service on port 20880 with custom protocol --> <dubbo:protocol name="myProtocol" port="20880"/>  
Copy the code

So when we start the producer-side project, Dubbo will call our custom MyProtocol class during service exposure to complete the corresponding logic processing.