Recently, I took some time to read the source code of Dubbo, hoping to record and share my understanding of Dubbo through writing articles. If there are some flaws or mistakes in this article, I hope you don’t hesitate to point them out.
Dubbo SPI introduced
Java SPI
You may need to have a brief understanding of the Java Service Provider Interface (SPI) mechanism before reading this article. A brief introduction: In object-oriented design, we advocate interface programming between modules. Different modules may have different concrete implementations, but to avoid excessive coupling between modules, we need an efficient service discovery mechanism to select concrete modules. SPI is such an interface based programming + policy pattern + configuration file solution that allows users to enable/replace specific implementations of modules according to their actual needs.
Improvements to the Dubbo SPI
Dubbo.gitbooks. IO /dubbo-dev-b… Dubbo’s extension point loading is enhanced from the JDK standard Service Provider Interface (SPI) extension point discovery mechanism. 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
In Dubbo, if an interface is tagged with the @spi annotation, we consider it to be an extension point in Dubbo. Extension points are the core of Dubbo SPI. Let’s talk about the implementation in terms of extension point loading, extension point auto-packaging, and extension point auto-assembly.
Dubbo SPI mechanism in detail
Dubbo extension point loading
If you read about Java SPI before reading this article, you probably recall that there is a directory like/meta-INF /services. In this directory there is a file named after the interface, and the content of the file is the fully qualified name of the interface concrete implementation class. Similar designs can be found in Dubbo.
- Meta-inf /services/ (compatible with JAVA SPI)
- Meta-inf/Dubbo/(Custom extension point implementation)
- Meta-inf/Dubbo /internal/ (Dubbo internal extension Point implementation)
Very good ~ now that we know where to load the extension point, recall how the JAVA SPI is loaded.
ServiceLoader<DubboService> spiLoader = ServiceLoader.load(XXX.class);
Copy the code
Similarly, there is a class ExtensionLoader in Dubbo for loading extension points. In this section, we’ll focus on how this class helps us load extension points. Let’s start with a short piece of code.
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Copy the code
A number of similar code snippets are used in the Dubbo implementation, and all we need to do is provide a Type to get an adaptive (more on that later) extension class for that type. When we get the corresponding adaptive extension class, we first get the ExtensionLoader of that type. Looking at this, we should subconsciously feel that there should be a corresponding ExtensionLoader object for each type. Let’s start by looking at how ExtensionLoader is fetched.
GetExtensionLoader ()
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!");
}
// Whether to be identified by SPI annotations
if(! withExtensionAnnotation(type)) {throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}
//EXTENSION_LOADERS is a ConcurrentMap collection. Key is a Class object and value is a ExtenLoader object
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
private ExtensionLoader(Class
type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
Copy the code
The above code is relatively simple, fetching the Loader from the EXTENSION_LOADERS collection according to type and creating a new ExtensionLoader object if the value returned is null. A similar method is used to get the objectFactory, which gets the ExtensionFactory adaptive class.
getAdaptiveExtension()
public T getAdaptiveExtension(a) {
//cachedAdaptiveInstance is used to cache instances of adaptive extension classes
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
// ...}}}}return (T) instance;
}
Copy the code
GetAdaptiveExtension () method is used to retrieve the current adaptive extended class instance is first obtained from the cachedAdaptiveInstance object, if the value is null createAdaptiveInstanceError is empty at the same time, The createAdaptiveExtension method is called to create an instance of the extension class. Update cachedAdaptiveInstance after creation.
createAdaptiveExtension()
private T createAdaptiveExtension(a) {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
// omit exception}}Copy the code
There are two methods are worthy of our attention, injectExtension () and getAdaptiveExtensionClass (). InjectExtension () to see the name as a method to realize the function of injection, and getAdaptiveExtensionClass () is used to obtain specific adaptive extension class. Let’s look at these two methods in turn.
injectExtension()
private T injectExtension(T instance) {
try {
if(objectFactory ! =null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
// Skip the DisableInject annotation if it exists
if(method.getAnnotation(DisableInject.class) ! =null) {
continue;
}
Method gets the type of the first argumentClass<? > 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("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ":" + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
Copy the code
A quick summary of what this method does: Iterates through the set method of the current instance, using the string from the fourth to the end of the set as the keyword, and tries to get the corresponding extended class implementation via objectFactory. If a corresponding extension class exists, it is injected into the current instance by reflection. This approach implements a simple dependency injection function, and this is where IOC in Dubbo is actually embodied.
getAdaptiveExtensionClass()
privateClass<? > getAdaptiveExtensionClass() { getExtensionClasses();if(cachedAdaptiveClass ! =null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
Copy the code
Then see getAdaptiveExtensionClass () method. First call getExtensionClasses () method, if cachedAdaptiveClass () is not null is returned, if null call createAdaptiveExtensionClass () method. Let’s look at these two methods in turn.
getExtensionClasses()
private Map<String, Class<? >>getExtensionClasses() { Map<String, Class<? >> classes = cachedClasses.get();if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if(classes == null) { classes = loadExtensionClasses(); cachedClasses.set(classes); }}}return classes;
}
Copy the code
To keep things simple, let’s look directly at the loadExtensionClasses() method.
privateMap<String, Class<? >> loadExtensionClasses() {final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation ! =null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
// There can be only one default extension instance
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ":" + Arrays.toString(names));
}
if (names.length == 1) {
cachedDefaultName = names[0]; } } } Map<String, Class<? >> extensionClasses =newHashMap<String, Class<? > > (); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
return extensionClasses;
}
Copy the code
After all this waffling, we’re finally getting to the point. Why are we getting to the point? Look at the values of these variables
- DUBBO_INTERNAL_DIRECTORY: meta-inf/dubbo/internal /
- DUBBO_DIRECTORY: meta-inf/dubbo /
- SERVICES_DIRECTORY: meta-inf/services /
Familiar formula familiar material. That’s right, we’re about to start reading the files in these three directories and start loading our extension points.
loadDirectory()
private void loadDirectory(Map
> extensionClasses, String dir, String type)
,> {
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if(classLoader ! =null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if(urls ! =null) {
while(urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); loadResource(extensionClasses, classLoader, resourceURL); }}}catch (Throwable t) {
// ...}}private void loadResource(Map
> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL)
,> {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
try {
String line;
while((line = reader.readLine()) ! =null) {
//# delete the existing comment
final int ci = line.indexOf(The '#');
if (ci >= 0) {
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
// The contents of the file are saved as key=value
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); }}catch (Throwable t) {
// ...}}}}finally{ reader.close(); }}catch (Throwable t) {
// ...}}private void loadClass(Map
> extensionClasses, java.net.URL resourceURL, Class
clazz, String name)
,> throws NoSuchMethodException {
// It is used to determine whether class is the implementation class of the type interface
if(! type.isAssignableFrom(clazz)) {throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
// Update the cachedAdaptiveClass cache object if the current class is marked by the @adaptive annotation
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if(! cachedAdaptiveClass.equals(clazz)) {// omit exception}}else if (isWrapperClass(clazz)) {
// Another mechanism of Dubbo extension points is involved here: wrapping, which is described laterSet<Class<? >> wrappers = cachedWrapperClasses;if (wrappers == null) {
cachedWrapperClasses = newConcurrentHashSet<Class<? > > (); wrappers = cachedWrapperClasses; } wrappers.add(clazz); }else {
clazz.getConstructor();
// If name is empty, call findAnnotationName(). If the current class has an @extension annotation, return the @extension annotation value directly;
// If there is no @extension annotation, but the class name is similar to xxxType(Type represents the class name of Type), the return value is lowercase XXX
if (name == null || name.length() == 0) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if(names ! =null && names.length > 0) {
The @activate annotation is used to configure the conditions under which the extension is automatically activated
// If the current class contains @activate, add it to the cache
Activate activate = clazz.getAnnotation(Activate.class);
if(activate ! =null) {
cachedActivates.put(names[0], activate);
} else {
// support com.alibaba.dubbo.common.extension.Activate
com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
if(oldActivate ! =null) {
cachedActivates.put(names[0], oldActivate); }}for (String n : names) {
if(! cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); } Class<? > c = extensionClasses.get(n);if (c == null) {
// Remember what the contents of the file look like? (name = calssValue), which we ended up saving into the extensionClasses collection
extensionClasses.put(n, clazz);
} else if(c ! = clazz) {// ...
}
}
}
}
}
Copy the code
This code is really quite long. And what he did was very simple:
- Splice generated file name: dir + type, read the file
- Read the contents of the file and split the contents into name and class strings
- If clazz class contains @adaptive annotation, add it to the cachedAdaptiveClass cache if clazz class is wrapper class, add it to wrappers if the file is not key=class, If pass @ the Extension notes will try to get the name clazz contains @ Activate annotations (mon compatible with com.alibaba.dubbo.com. The Extension. Activate annotations), Add it to the cachedEFFECTIVENESS cache
- Finally, add name as key and clazz as vlaue to the extensionClasses collection and return
Gets the adaptive extension class
getAdaptiveExtensionClass()
privateClass<? > getAdaptiveExtensionClass() { getExtensionClasses();if(cachedAdaptiveClass ! =null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
Copy the code
Ok, we have analyzed the getExtensionClasses method and loaded the extension point implementation into the cache. This method is by getAdaptiveExtensionClass () method of derivation, it looks like creating adaptive extension class. So if the cache object cachedAdaptiveClass is empty, when is it initialized? Review the previous code:
private void loadClass(Map
> extensionClasses, java.net.URL resourceURL, Class
clazz, String name)
,> throws NoSuchMethodException {
/ / to omit...
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if(! cachedAdaptiveClass.equals(clazz)) {/ / to omit...}}}Copy the code
If the current Clazz is found to contain the @adaptive annotation in the loadClass() method, the current clazz is saved as a cache Adaptive class. For example, we do this in the AdaptiveExtensionFactory class, which we cache as an adaptive class of ExtensionFactory type.
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory
Copy the code
We continue to analyze the latter part of the method. If cachedAdaptiveClass is null, then invokes the createAdaptiveExtensionClass () method of dynamically generated a adaptive extended class.
privateClass<? > createAdaptiveExtensionClass() { String code = createAdaptiveExtensionClassCode(); System.out.println(code); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();return compiler.compile(code, classLoader);
}
Copy the code
This piece of code is not going to be highlighted in this share, but it can be simply understood that Dubbo helped me generate an adaptive class. I took a snippet of generated code that looks like this:
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
private static final org.apache.dubbo.common.logger.Logger logger = org.apache.dubbo.common.logger.LoggerFactory.getLogger(ExtensionLoader.class);
private java.util.concurrent.atomic.AtomicInteger count = new java.util.concurrent.atomic.AtomicInteger(0);
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");
}
return extension.getInvoker(arg0, arg1, arg2);
}
public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) 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.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");
}
return extension.getProxy(arg0, arg1);
}
public java.lang.Object getProxy(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.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.getProxy(arg0); }}Copy the code
extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtens ion(extName);Copy the code
This is actually the essence of an adaptive adaptation class, but where does extName come from?
String extName = url.getParameter("proxy"."javassist");
Copy the code
ExtName, in turn, is derived from urls, which are actually a very important context transport for Dubbo, as you’ll see in the next series of articles.
public T getExtension(String name) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
// Read the extension implementation class from the cache
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
The above logic is relatively simple and will not be repeated here. Look directly at the createExtension() method.
private T createExtension(String name) { Class<? > clazz = getExtensionClasses().get(name);if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set<Class<? >> wrapperClasses = cachedWrapperClasses;if(wrapperClasses ! =null && !wrapperClasses.isEmpty()) {
for (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 getExtensionClasses() method was analyzed earlier, but it’s worth noting that: GetExtensionClasses return nothing more than a Class loaded with class.forname (), executing a static snippet of code instead of an actual instance. The actual instance object still needs to be retrieved by calling the class.newinstance () method. Now that we know what we’re doing, we’re using getExtensionClasses() to try to get a class object that’s already loaded by the system, and we’re using the class object to extend the instance cache. If the extended instance is null, call the newInstance() method to initialize the instance and place it in the EXTENSION_INSTANCES cache. The injectExtension() method is then called for dependency injection. The last paragraph covers the use of wrapper classes, which will be covered in the next section.
Extend the wrapper for the class
In the createExtension() method there is the following code:
private T createExtension(String name) {
//... omitSet<Class<? >> wrapperClasses = cachedWrapperClasses;if(wrapperClasses ! =null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
//... omit
}
Copy the code
Remember where wrapperClasses were initialized? We’ve seen this in the loadClass() method above. To recap:
private void loadClass(Map
> extensionClasses, java.net.URL resourceURL, Class
clazz, String name)
,> throws NoSuchMethodException {
//... omit
if(isWrapperClass(clazz)) { Set<Class<? >> wrappers = cachedWrapperClasses;if (wrappers == null) {
cachedWrapperClasses = newConcurrentHashSet<Class<? > > (); wrappers = cachedWrapperClasses; } wrappers.add(clazz); }//... omit
}
private boolean isWrapperClass(Class
clazz) {
try {
clazz.getConstructor(type);
return true;
} catch (NoSuchMethodException e) {
return false; }}Copy the code
Before we look at this method let’s look at the wrapper class definition in Dubbo. Here’s an example:
class A {
private A a;
public A(A a){
this.a = a; }}Copy the code
We can see that class A has A constructor that takes A, which we call the copy constructor. A class with such a constructor is called a Wrapper class in Dubbo. Continue with the isWrapperClass() method, which is simpler and tries to get the constructor in Clazz that takes Type. If it does, clazz is considered a wrapper for the current Type class. Combined with the above code, we see that we cache the wrapper class corresponding to type when loading the extension point.
private T createExtension(String name) {
//... omit
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set<Class<? >> wrapperClasses = cachedWrapperClasses;if(wrapperClasses ! =null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
//... omit
}
Copy the code
To better understand this code, let’s assume that the current type value is protocol.class, We can in the org. Apache. Dubbo. RPC. The Protocol file found in the packing of the Protocol interface class ProtocolFilterWrapper and ProtocolListenerWrapper, They are in turn added to the cachedWrapperClasses collection. Iterating through the collection of cachedWrapperClasses, such as the first time a ProtocolFilterWrapper class is fetched, the instance is wrapped in a copy constructor that calls ProtocolFilterWrapper. After creating an instance of the ProtocolFilterWrapper object, call injectExtension() for dependency injection. At this point, instance is an instance of ProtocolFilterWrapper. Continue the loop and wrap the ProtocolFilterWrapper class in the ProtocolListenerWrapper class. So we end up returning a ProtocolListenerWrapper instance. When finally called, the method of the original instance will still be called through layer by layer. The wrapper class here is a bit like AOP in that we can wrap it layer by layer to add custom operations like logging, monitoring, and so on before invoking the extended implementation.
IOC mechanism in Dubbo
We have already discussed the use of reflection to implement an IOC-like function in Dubbo. In this section, we revisit the injectExtension() method and take a closer look at the implementation of IOC functionality in Dubbo.
createExtension()
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
private T injectExtension(T instance) {
/ /...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); }}/ /...
}
public class StubProxyFactoryWrapper implements ProxyFactory {
// ...
private Protocol protocol;
public void setProtocol(Protocol protocol) {
this.protocol = protocol;
}
/ /...
}
Copy the code
We covered the Wrapper class in the previous chapter, so let’s use an example here. For example, if our current wrapperClass class is StubProxyFactoryWrapper, the code execution logic would look something like this:
- Create StubProxyFactoryWrapper instance;
- Get the instance created by flow 1 as a parameter to injectExtension(), execute;
- InjectExtension () loops through the setProtocol() method of StubProxyFactoryWrapper (where pt= protocol.class, property= Protocol), Perform the objectFactory. GetExtension (pt, property) method. ObjectFactory is initialized in the constructor of ExtensionLoader, where the adaptive extension class is obtained as AdaptiveExtensionFactory.
- Perform AdaptiveExtensionFactory. GetExtension (). The AdaptiveExtensionFactory class has a collection variable Factories. Factories are initialized in the constructor of AdaptiveExtensionFactory and contain two factory classes: SpiExtensionFactory and SpringExtensionFactory. The getExtension() method of the AdaptiveExtensionFactory class calls the getExtension() method of the SpiExtenFactory and SpringExtensionFactory classes in turn.
- Execute the getExtension() method of SpiExtensionFactory. Type =Procotol. Class,property=protocol protocol is an interface class annotated with @spi. The ExtensionLoader object of type Protocol is retrieved and the loader’s getAdaptiveExtension() method is called. The Adaptive class is Protocol$Adaptive dynamic class.
- objectFactory.getExtension(pt, property); The resulting class is Protocol$Adaptive, which is then injected into the StubProxyFactoryWrapper instance using reflection.
@SPI("dubbo")
public interface Protocol {}public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if(! loader.getSupportedExtensions().isEmpty()) {returnloader.getAdaptiveExtension(); }}return null; }}Copy the code
END
Finally, let’s go back to the beginning of Dubbo SPI’s improvement over JAVA SPI:
Dubbo’s extension point loading is enhanced from the JDK standard Service Provider Interface (SPI) extension point discovery mechanism. 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.
The summary is as follows:
- When Dubbo SPI loads the extension point, it stores the extension Class in the cache as a key-value, but the extension Class is only loaded by calling class.forname () and is not instantiated. The extension class is instantiated when the getExtension() method is called.
- Dubbo implements dependency injection through the factory pattern and reflection mechanism.
- AOP mechanisms are implemented in Dubbo through wrapper classes that make it easy to add monitoring and print logs.