Dubbo SPI core implementation
- Introduction to Dubbo SPI implementation
- ExtensionFactory interface
- ExtensionLoader SPI core implementation
- conclusion
Dubbo SPI implementation
Due to Java SPI implementation is insufficient, so Dubbo own implementation of a set of SPI, Dubbo SPI implementation application startup will implement the SPI interface extension class load into the cache, the following is a simple look at the core source code Dubbo SPI implementation
ExtensionFactory interface
ExtensionFactory interface, which provides the core interface to access the SPI factory
@SPI
public interface ExtensionFactory {
/**
* Get extension.
*
* @param type object type.
* @param name object name.
* @return object instance.
*/
<T> T getExtension(Class<T> type, String name);
}
Copy the code
ExtensionLoader Dubbo SPI core implementation class
getExtensionLoader
Dubbo’s SPI is loaded when the program is started and the service is registered. The getExtensionLoader method is called through a private constructor. The main logic of this method is as follows:
type
Whether is emptytype
Interface typetype
Class with@SPI
annotations- After passing the verification will be from
EXTENSION_LOADERS
Gets the extension instance from the cache of the extension implementation - The code doesn’t get it at first, so it goes to
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
To initialize and cache the extended instance.
Private static final ConcurrentMap<Class<? >, ExtensionLoader<? >> EXTENSION_LOADERS = new ConcurrentHashMap<>(64); @SuppressWarnings("unchecked") 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 an interface!" ); } if (! withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!" ); } // Implement ExtensionLoader<T> loader = (ExtensionLoader<T>) extension_loaders.get (type); PutIfAbsent (type, new ExtensionLoader<T>(type)); if (loader == null) {// Add extension_loaders. putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }Copy the code
ExtensionLoader private constructor
The ExtensionLoader class is initialized when getExtensionLoader calls the private constructor. The code logic is as follows:
- Instantiate properties that initially instantiate classes but are empty collections;
- When the private constructor is first executed
ExtensionLoader
When,type
It will be something elseSPI
Interface, that’s when you go toExtensionLoader.getExtensionLoader(ExtensionFactory.class)
“, will speak at this pointExtensionFactory
Add to cache; - call
getAdaptiveExtension
Method to obtain an instance of the adaptive extension point and copy the obtained instance to the objectFactory, which is contained in the collection returned by debugSpiExtensionFactory
andSpringExtensionFactory
.
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> cachedActivates = new ConcurrentHashMap<String, Activate>(); private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>(); private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>(); // Private ExtensionLoader(Class<? > type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }Copy the code
GetAdaptiveExtension method
The method obtains the adaptive extension point, and the code is as follows:
Public T getAdaptiveExtension () {/ / adaptive extension point was obtained from the cache Object instance Object instance = cachedAdaptiveInstance. The get (); // Not available. Adopt double check if the instance (= = null) {if (createAdaptiveInstanceError = = null) {/ / the cache using the object's monitor (lock) synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); If (instance == null) {try {// Create an adaptive extension point object instance = createAdaptiveExtension(); / / the object cache cachedAdaptiveInstance. Set (instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }Copy the code
CreateAdaptiveExtension method
Adaptive extension point object, and create the HTML code is as follows, here, to watch injectExtension method later will look at the source of this method, and analyzes in detail its functions, the following code through getAdaptiveExtensionClass (). The newInstance () to create the adaptive extension point of the object
private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); }}Copy the code
GetAdaptiveExtensionClass method
Will first call getExtensionClasses () to load the extension of class (detailed process analysis), if the adaptive extended class cache is not null return adaptive extended class, otherwise call createAdaptiveExtensionClass () method to create adaptive extension class.
private Class<? > getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass ! = null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }Copy the code
CreateAdaptiveExtensionClass method
The code for this method is as follows, which creates an adaptive extension class. Code analysis can be done by referring to the comments in the code
private Class<? > createAdaptiveExtensionClass () {/ / to create adaptive class code, unknown look here, this method is through the dynamic proxy generated proxy class, Using reflection for the bytecode enhancement operation String code = createAdaptiveExtensionClassCode (); ClassLoader ClassLoader = findClassLoader(); // Get the bytecode enhanced compiler, where the bytecode enhanced compiler is also extensible, Here the default use javassist com.alibaba.dubbo.common.compiler.Com piler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); Return compiler.compile(code, classLoader); // Use the bytecode enhanced compiler to create class return compiler.compile(code, classLoader). }Copy the code
Load extensions from configuration files to implement source code analysis
This method starts by fetching the supported extensions from the cachedClasses cache. If not, load the extension point from the fixed path extension configuration file. The code is as follows:
- GetExtensionClasses: Attempts to retrieve an extension point from the cache. The extension point’s class information is stored in a Java bean that you define and is called when it is not available from the cache
loadExtensionClasses
Method loads the extension point class and caches it - LoadExtensionClasses: First determines whether the Type class exists
SPI
Annotations, if there is a value it checks the value of the annotation if there is a value it caches the default extension class name, and then it loads the extension point from the fixed path, which is hereMETA-INF/dubbo/internal/
.META-INF/dubbo/
.META-INF/services/
- LoadDirectory: gets the class loader, according to
URL
Load extension point - LoadResource: Loads extensible interfaces to
cachedNames
In the - LoadClass: Determines whether the loaded class is a Wapper class. If yes, it is put into the Wapper class cache. Otherwise, load the class directly to
cachedNames
In the
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; } // synchronized in getExtensionClasses private Map<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); 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 = new HashMap<String, Class<? > > (); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadDirectory(extensionClasses, DUBBO_DIRECTORY); loadDirectory(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; } private void loadDirectory(Map<String, Class<? >> extensionClasses, String dir) { String fileName = dir + type.getName(); 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) { logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); } } private void loadResource(Map<String, Class<? >> 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) { final int ci = line.indexOf('#'); if (ci >= 0) line = line.substring(0, ci); line = line.trim(); if (line.length() > 0) { try { String name = null; 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) { IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); } } private void loadClass(Map<String, Class<? >> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { 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."); } if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (! cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else if (isWrapperClass(clazz)) { Set<Class<? >> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<? > > (); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } else { clazz.getConstructor(); 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) { Activate activate = clazz.getAnnotation(Activate.class); if (activate ! = null) { cachedActivates.put(names[0], activate); } for (String n : names) { if (! cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); } Class<? > c = extensionClasses.get(n); if (c == null) { extensionClasses.put(n, clazz); } else if (c ! = clazz) { throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } } } } } private boolean isWrapperClass(Class<? > clazz) { try { clazz.getConstructor(type); return true; } catch (NoSuchMethodException e) { return false; }}Copy the code
InjectExtension method
The main purpose of this method is to implement dependency injection. The code and process are as follows:
- Filtered out by reflection
set
The way to start - When judging by
DisableInject
Embellishment, if embellished then no dependency injection - Obtain the class information and obtain the attribute information based on the class information
property
- Instantiate the extension class
- call
invoke
Method, which calls the instance objectsetter
Method, in this case by reflection
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())) { /** * Check {@link DisableInject} to see if we need auto injection for this property */ if (method.getAnnotation(DisableInject.class) ! = null) { continue; } 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("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
The AdaptiveExtensionFactory class getExtension method
The createExtension() method is used to load the object instance. The code and processing flow are as follows:
- The extension name is as follows
Dubbo in dubbo (@ SPI (" "))
Don’t empty - If the extension is micro
true
Return the default extension, the default extension code is not shown, see the code logic will returnnull
- Get the extension object from the extension implementation instance cache
Holder
, if it is empty, the extension name andHolder
Object into the cache and return the holder with the extension get()
Extend the object, using double check, if the object is empty then passcreateExtension(name)
Create this object andset
toholder
In the
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
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
CreateExtension method
The code and process are as follows:
- Gets the name of the extension class based on the extension name
Class
- Gets an object instance of the extended class from the cache
- If the instance is empty, create it by reflection and place it
EXTENSION_INSTANCES
The cache injectExtension
The method is explained earlier- If the wrapper class cache is not empty, it traverses the wrapper cache and creates a wrapper class object
org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
.listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
Etc.
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
conclusion
I have spent two days on and off to sort out part of Dubbo SPI. In fact, there are still many source codes to be disassembled. If you are interested, you can debug the code privately to understand the process of Dubbo SPI. If you want to implement an SPI mechanism modeled after Dubbo, I recommend reading the SPI implementation of soul Gateway, another open source middleware. The SPI implementation of Soul Gateway is essentially a replica of dubbo SPI implementation, but it is much simpler than Dubbo. More friendly than Dubbo for beginners and friends who quickly understand SPI.