Dubbo extends the mechanism SPI
The previous article “Dubbo source code analysis (1) Hello,Dubbo” is a general introduction to the whole dubbo project, and from this article, I will interpret the implementation principle and characteristics of dubbo and each module from the source code, because all the way to interpret the source code by screenshots will lead to a messy article, so I will only put part of the screenshots, The full interpretation will be updated in sync with the dubbo source code at fork on Github, and I will also include hyperlinks to key points in the article for quick reference.
I will write a goal at the beginning of each post so that the reader can know at a glance if the article is what you are looking for.
Objective: to let readers know the JDK SPI ideas, DUbbo SPI ideas, dubbo extension mechanism SPI principle, to be able to read the source code to achieve extension mechanism.
The first source analysis of the article will first talk about the principle of DUbbo extension mechanism SPI, browsing dubbo official documents of friends must know that Dubbo has a large number of SPI extension implementation, including protocol extension, call interception extension, routing extension and other 26 extensions, and SPI mechanism used in each module design. So I’m going to start by explaining Dubbo’s extension mechanism, SPI.
The IDEA behind SPI in the JDK
SPI’s full name is Service Provider Interface. In object-oriented design, it is recommended to program modules based on interfaces rather than hard coding implementation classes. This is also for pluggable module design principles. A mechanism for service discovery is needed in order to assemble a module without specifying which implementation it is. The JDK’s SPI is to find a service implementation for an interface. The JDK provides a service implementation lookup utility class: java.util.serviceloader, which loads the meta-INF /service/ configuration file. The specific internal implementation logic is not expanded here, but mainly explains dubbo’s implementation principle of SPI.
Dubbo’s SPI extension mechanism principle
Dubbo himself implemented an SPI mechanism that improved on the STANDARD SPI mechanism of the JDK:
- The JDK’s standard SPI can only be found and instantiated through history, potentially leading to all extension points being loaded at once, which can lead to a waste of resources if not all of them are used. Dubbo has multiple implementations for each extension point, For example com. Alibaba. Dubbo, RPC Protocol interface has InjvmProtocol, DubboProtocol, RmiProtocol, HttpProtocol, HessianProtocol implementation, if we can only use one of the implementation, However, loading all implementations can lead to a waste of resources.
- Expanded in the configuration file format changes, such as the meta-inf/dubbo/com. XXX. Com in the Protocol. The foo = com XxxProtocol format change to XXX. Foo XxxProtocol this in the form of key-value pairs, The aim is to make us more easy to locate the problem, such as due to the third-party libraries does not exist, cannot be initialized, result in unable to load the extension (” A “), when A user configuration USES A, dubbo will be unable to load the extension of the error, not load at which the realization of the extension of failure and the causes of errors, This is because the original configuration format did not record the id of the extension, so Dubbo could not throw a more accurate exception, which made troubleshooting more difficult. Therefore, the configuration is performed in the key-value format.
- Dubbo’s SPI mechanism adds support for IOC and AOP, and one extension point can be injected directly into other extension points via setters.
Let’s start by looking at the structure of the SPI extension mechanism implementation:
(I) Annotation @spi
Annotating an interface with @spi indicates that the interface is extensible. <dubbo: Protocol />, <dubbo:service />, <dubbo:reference />, <dubbo:reference /> By default, DubboProtocol is the interface Protocol, because Protocol has an @spi (“dubbo”) annotation on it. The protocol attribute value or default values will be treated as a key in the interface implementation class, go meta-inf \ dubbo dubbo \ internal \ com alibaba. Dubbo. RPC. The protocol file to find the key corresponding value, see below:
Value is the implementation class of the Protocol interface, DubboProtocol, which does the SPI extension.
(2) Adaptive
The annotations in order to guarantee the dubbo when internal call specific implementation is not hard-coded to specify which reference implementation, and is adapted to a variety of implementation, to do so in accordance with pluggable module interface design principles, has increased the flexibility of the framework, the annotations are also realize the characteristics of the automatic assembly of extension points.
Dubbo provides two ways to implement an interface adapter:
-
The @Adaptive annotation above the implementation class indicates that the implementation class is an adapter for the interface.
For example, the ExtensionFactory interface in Dubbo has an implementation class AdaptiveExtensionFactory. With the @adaptive annotation, AdaptiveExtensionFactory does not provide specific business support. Both SpiExtensionFactory and SpringExtensionFactory implementations are used to accommodate ExtensionFactory. AdaptiveExtensionFactory selects which implementation of the ExtensionFactory to call based on some state at run time, as shown in Adaptive’s code parsing below.
-
By annotating @Adaptive on the interface method, Dubbo dynamically generates the adapter class.
The Transporter interface source code explains this approach:
You can see that there are @adaptive annotations on the bind and connect methods of this interface. The parameters of the methods with this annotation must contain the URL. ExtensionLoader will pass createAdaptiveExtensionClassCode method that dynamically generate a class from the $the Adaptive, the generated code is as follows:
package com.alibaba.dubbo.remoting; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter{ public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException { // If the URL argument is empty, an exception is thrown. if (arg0 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg0; // The getParameter method can be viewed in the source code String extName = url.getParameter("client", url.getParameter("transporter"."netty")); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])"); // I'll talk more about this later com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName); return extension.connect(arg0, arg1); } public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException { if (arg0 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg0; String extName = url.getParameter("server", url.getParameter("transporter"."netty")); if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])"); com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName); returnextension.bind(arg0, arg1); }}Copy the code
You can see that the two methods of this class are the two annotated methods in the Transporter interface. Let me explain the first method, connect:
- All extension points carry configuration information by passing the URL, so methods in the adapter must carry URL parameters in order to select the corresponding extension implementation based on the configuration in the URL.
- The @adaptive annotation has some key values. For example, the connect annotation has two keys, “client” and “transporter”. The URL will first fetch the corresponding value of the client as the key value in the @spi ** annotation. If it is empty, the transporter value is fetched. If it is still empty, the extended implementation class is invoked based on the SPI default key, which is netty. If @spi does not set a default value, an IllegalStateException is thrown.
It is clear how the adapter to choose which implementation class as a class of this you need to call, here is the most critical or highlighted the dubbo to URL for the bus, in the process of running state of all the data and information could be obtained via the URL, such as what current system USES serialization, use what communication, use the information such as what kind of load balancing, All are presented by THE PARAMETERS of URL. Therefore, during the running of the framework, the corresponding data needed at a certain stage can be obtained from the parameter list of URL through the corresponding Key.
Annotation @activate
Extension point automatic loading annotation is used to control whether the extension point implementation is automatically activated and loaded. It is used on the extension implementation class to realize the automatic activation feature of the extension point. It can set two parameters, namely group and value. For details, please refer to the official documentation.
Extension point Automatic activation address: dubbo.apache.org/zh-cn/docs/…
(4) interface ExtensionFactory
Take a look at its source code:
This interface is the Extension Factory Interface class, which is itself an extension interface, annotated by SPI. The factory interface provides access to an instance of the implementation class. It also has two extension implementations, SpiExtensionFactory and SpringExtensionFactory, which represent two different ways to access the instance. The specific method of obtaining an instance of the implementation class is specified in the AdaptiveExtensionFactory. Specific rules to see the following source code analysis.
(5) ExtensionLoader
This class is the ExtensionLoader, which is the core of dubbo’s implementation of SPI extension mechanisms and so on. Almost all of the implementation logic is encapsulated in ExtensionLoader.
Detailed code comments can be found at github: github.com/CrazyHZM/in…
-
Attributes (select key attributes to expand the explanation, see github comment for the rest)
-
About the path variable to the configuration file:
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/"; Copy the code
“Meta-inf /services/”,” meta-INF /dubbo/”, “meta-INF /dubbo/internal/” This is where the configuration file named with the fully qualified name of the interface is stored, as I mentioned in annotation @spi ** above. “Meta-inf /dubbo/internal/” is the configuration file path of the extension provided by Dubbo. “Meta-inf /dubbo/” is used to store configuration files for user-defined extensions.
-
Set of extension loaders where key is the extension interface, such as Protocol:
private static finalConcurrentMap<Class<? >, ExtensionLoader<? >> EXTENSION_LOADERS =newConcurrentHashMap<Class<? >, ExtensionLoader<? > > ();Copy the code
-
Set of extension implementation classes. Key is the extension implementation Class and value is the extension object. For example, key is the Class and value is the DubboProtocol object
private static finalConcurrentMap<Class<? >, Object> EXTENSION_INSTANCES =newConcurrentHashMap<Class<? >, Object>();Copy the code
-
The following attributes start with cache, and are cached for performance and resource optimization. After reading the extension configuration, the cache is first cached, and when an implementation is needed, the object of that implementation class is initialized, and then the object is cached.
// The extensions mentioned below are key values in the configuration file, such as "dubbo", etc // Cache extensions are mapped to extended classes, swapping keys and values for cachedClasses. private finalConcurrentMap<Class<? >, String> cachedNames =newConcurrentHashMap<Class<? >, String>();// A collection of extended implementation classes for caching private finalHolder<Map<String, Class<? >>> cachedClasses =newHolder<Map<String, Class<? > > > ();// The extension is mapped to the automatic activation class with @activate private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>(); // A collection of cached extension objects. Key is the extension name and value is the extension object // For the Protocol extension, key is dubbo and value is DubboProcotol private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object // Cache Adaptive extension objects, such as those of the AdaptiveExtensionFactory class private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); // The cached adaptive extension object class, such as the AdaptiveExtensionFactory class private volatileClass<? > cachedAdaptiveClass =null; // The default extension of the cache is the value set in @spi private String cachedDefaultName; // Create cachedAdaptiveInstance private volatile Throwable createAdaptiveInstanceError; // Extend the Wrapper implementation class collection privateSet<Class<? >> cachedWrapperClasses;// The mapping between the extension name and the exception that is loaded for the extension class private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>(); Copy the code
The Wrapper class concept is mentioned here. The Wrapper class also implements the extension interface, but the purpose of the Wrapper class is for ExtensionLoader to return an extension point wrapped outside the actual extension point implementation, which implements automatic extension point wrapping. In plain English, an interface has many implementation classes, and these implementation classes will have some common logic. If you write the common logic in each implementation class, the code will be repeated. Therefore, we add the Wrapper class to wrap the common logic in the Wrapper class, which is similar to the idea of AOP aspect programming. This part of the explanation can also be combined with official documents:
Extension point autowrap features address: dubbo.apache.org/zh-cn/docs/…
-
-
GetExtensionLoader (Class Type) : Obtains the extension loader based on the extension point interface.
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { // The extension point interface is empty, throwing an exception if (type == null) throw new IllegalArgumentException("Extension type == null"); // Check whether type is an interface class if(! type.isInterface()) {throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } // Determine whether the interface is extensible if(! withExtensionAnnotation(type)) {throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); } // Retrieve the extension loader corresponding to the extension interface from the extension loader collection ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); // If empty, create an extension loader for the extension interface and add it to EXTENSION_LOADERS if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; } Copy the code
This method of source code analysis to see above, interpretation or not too many difficulties. It’s just a matter of figuring out what a couple of attributes mean.
-
GetActivateExtension method: Gets a collection of extension implementation class objects that meet the criteria for automatic activation
/** * Get the set of extended implementation class objects that meet auto-activation criteria (for auto-activation classes without group criteria) *@param url * @param key * @return* / public List<T> getActivateExtension(URL url, String key) { return getActivateExtension(url, key, null); } / / abandoned public List<T> getActivateExtension(URL url, String[] values) { return getActivateExtension(url, values, null); } /** * Get the set of extended implementation objects that meet the auto-activation criteria (for auto-activation classes with value and group criteria) *@param url * @param key * @param group * @return* / public List<T> getActivateExtension(URL url, String key, String group) { String value = url.getParameter(key); // Get an array of extended objects that meet the criteria for automatic activation return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group); } public List<T> getActivateExtension(URL url, String[] values, String group) { List<T> exts = new ArrayList<T>(); List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values); // Check that '-name' does not exist. // For example,
, all default filters are removed. if(! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {// Get the extension implementation class array and put the extension implementation classes in cachedClasses getExtensionClasses(); for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) { String name = entry.getKey(); Activate activate = entry.getValue(); // Check whether the group value exists in the group group of all automatic activation classes if (isMatchGroup(group, activate.group())) { // Get the extension object with the extension name T ext = getExtension(name); // Not included in the custom configuration. If it does, it is handled in the code below. // Check whether the configuration is removed. For example, , MonitorFilter is removed // Check whether it is activated if (!names.contains(name) && !names.contains(Constants.REMOVE_VALUE_PREFIX + name) && isActive(activate, url)) { exts.add(ext); } } } / / sorting Collections.sort(exts, ActivateComparator.COMPARATOR); } List<T> usrs = new ArrayList<T>(); for (int i = 0; i < names.size(); i++) { String name = names.get(i); // Check whether the configuration is removed if(! name.startsWith(Constants.REMOVE_VALUE_PREFIX) && ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {// Put the custom configuration in front of the auto-activated extension object in the configuration, so that the custom configuration is loaded first // For example, , DemoFilter will be placed before the default filter. if (Constants.DEFAULT_KEY.equals(name)) { if(! usrs.isEmpty()) { exts.addAll(0, usrs); usrs.clear(); }}else{ T ext = getExtension(name); usrs.add(ext); }}}if(! usrs.isEmpty()) { exts.addAll(usrs); }return exts; } Copy the code It can be seen that getActivateExtension overloads four methods. In fact, the final implementation of getActivateExtension overloads the last method, because the conditions for automatic activation of classes can be divided into unconditional, value only, and group and value. For details, please refer to @activate.
The final getActivateExtension method has a few key points:
- The value of group is valid because group can be “provider” or “consumer”.
- Check whether the configuration is removed.
- If you have a custom configuration that needs to be stored before the automatic activation extension implementation object is loaded, you need to store the custom configuration first.
-
GetExtension method: Get the extension object by the extension name
/** * Get extension object * by extension@param name * @return* / @SuppressWarnings("unchecked") public T getExtension(String name) { if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); // Find the default extension implementation, that is, the default value in @spi as key if ("true".equals(name)) { return getDefaultExtension(); } // Get the corresponding extension object 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) { // Create the object of the interface implementation class with the extension instance = createExtension(name); // Put the created extension object into the cacheholder.set(instance); }}}return (T) instance; } Copy the code
This method involves the getDefaultExtension and createExtension methods, which are covered later. The other logic is simply to fetch it from the cache, create it if it doesn’t exist, and then put it in the cache.
-
GetDefaultExtension method: Find the default extension implementation
public T getDefaultExtension(a) { // Get an array of implementation classes for the extension interface getExtensionClasses(); if (null == cachedDefaultName || cachedDefaultName.length() == 0 || "true".equals(cachedDefaultName)) { return null; } // call getExtension again return getExtension(cachedDefaultName); } Copy the code
This involves the getExtensionClasses method, which I’ll cover later. To obtain the default implementation object is to obtain the implementation object from the default extension name in the cache.
-
AddExtension method: An implementation class that extends the interface
public void addExtension(String name, Class clazz) { getExtensionClasses(); // load classes // Is the class itself or a subclass of the interface if(! type.isAssignableFrom(clazz)) {throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type); } // Whether the class is activated if (clazz.isInterface()) { throw new IllegalStateException("Input type " + clazz + "can not be interface!"); } // Determine whether it is an adapter if(! clazz.isAnnotationPresent(Adaptive.class)) {if (StringUtils.isBlank(name)) { throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); } if (cachedClasses.get().containsKey(name)) { throw new IllegalStateException("Extension name " + name + " already existed(Extension " + type + ")!"); } // Put the extension name and implementation class of the extension interface into the cache cachedNames.put(clazz, name); cachedClasses.get().put(name, clazz); } else { if(cachedAdaptiveClass ! =null) { throw new IllegalStateException("Adaptive Extension already existed(Extension " + type + ")!"); } cachedAdaptiveClass = clazz; }}Copy the code
-
The getAdaptiveExtension method: Gets the adaptive extension object, which is the adapter object for the interface
@SuppressWarnings("unchecked") public T getAdaptiveExtension(a) { Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { // Create an adapter object instance = createAdaptiveExtension(); 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
The idea is to fetch an adapter object from the cache. If not, create an adapter object and place it in the cache. The createAdaptiveExtension method is explained below.
-
CreateExtension method: Creates an object of the extension interface implementation class with the extension name
@SuppressWarnings("unchecked") private T createExtension(String name) { // Get the extension implementation class for the extension nameClass<? > clazz = getExtensionClasses().get(name);if (clazz == null) { throw findException(name); } try { // See if there are any objects of this class in the cache T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // Inject dependent properties into the object (autowiring) injectExtension(instance); // Create the Wrapper extension object (automatic wrapping)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 features of two extension points are used here, namely automatic assembly and automatic packaging. InjectExtension method resolution is given below.
-
InjectExtension method: Inject the properties of the dependencies into the created extension
private T injectExtension(T instance) { try { if(objectFactory ! =null) { // Reflection gets all the methods in that class for (Method method : instance.getClass().getMethods()) { // If set is used if (method.getName().startsWith("set") && method.getParameterTypes().length == 1&& Modifier.isPublic(method.getModifiers())) { Class<? > pt = method.getParameterTypes()[0]; try { The StubProxyFactoryWrapper class has the Protocol Protocol property, String property = method.getName().length() > 3 ? method.getName().substring(3.4).toLowerCase() + method.getName().substring(4) : ""; // Get a property value, such as a Protocol object, or possibly a Bean object Object object = objectFactory.getExtension(pt, property); if(object ! =null) { // Inject dependency propertiesmethod.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 idea is to get all the methods in the class by reflection, then find the set method, find the properties that need dependency injection, and then inject the object into it.
-
GetExtensionClass method: gets the extension implementation class corresponding to the extension name
privateClass<? > getExtensionClass(String name) {if (type == null) throw new IllegalArgumentException("Extension type == null"); if (name == null) throw new IllegalArgumentException("Extension name == null"); Class<? > clazz = getExtensionClasses().get(name);if (clazz == null) throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!"); return clazz; } Copy the code
Here is the method that calls getExtensionClasses, explained below.
-
GetExtensionClasses method: Gets an array of extension implementation classes
privateMap<String, Class<? >> getExtensionClasses() { Map<String, Class<? >> classes = cachedClasses.get();if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { // Load the extension implementation class array from the configuration fileclasses = loadExtensionClasses(); cachedClasses.set(classes); }}}return classes; } Copy the code
If the cache is empty, the extension implementation class is read from the configuration file. The loadExtensionClasses method is resolved below.
-
LoadExtensionClasses method: Loads an array of extended implementation classes from the configuration file
privateMap<String, Class<? >> loadExtensionClasses() {final SPI defaultAnnotation = type.getAnnotation(SPI.class); if(defaultAnnotation ! =null) { // the default value in @spi String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); // Only one default value is allowed 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]; }}// Load the implementation class array from the configuration fileMap<String, Class<? >> extensionClasses =newHashMap<String, Class<? > > (); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadDirectory(extensionClasses, DUBBO_DIRECTORY); loadDirectory(extensionClasses, SERVICES_DIRECTORY);return extensionClasses; } Copy the code
The first part of the logic is to put the default values in the SPI annotations into the cache, the logic to load the implementation class array is in the next few lines, the key is the loadDirectory method (parsed below), and here you can look to find the order of the resource paths accessed by the configuration file.
-
LoadDirectory method: loads an array of extended implementation classes from a configuration file
private void loadDirectory(Map
> extensionClasses, String dir) ,> { // Concatenate the fully qualified name of the interface to get the complete filename String fileName = dir + type.getName(); try { Enumeration<java.net.URL> urls; // Get ExtensionLoader class information ClassLoader classLoader = findClassLoader(); if(classLoader ! =null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if(urls ! =null) { // Walk through the file 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); }}Copy the codeThe idea here is to get the complete file name, iterate through each file, and load the contents of each file in the loadResource method.
-
LoadResource method: loads the contents of a file
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) { // Skip the content annotated by # 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; int i = line.indexOf('='); if (i > 0) { // Split key and value according to "=" name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { // Load the extension class 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); }}Copy the codeThe main logic of this class is to read the contents, skip the “#” comment, split the contents according to the key=value form in the configuration file, and load the corresponding value class.
-
LoadClass method: loads extension classes based on the value in the configuration file
private void loadClass(Map
> extensionClasses, java.net.URL resourceURL, Class clazz, String name) ,> throws NoSuchMethodException { // Whether the class implements an extension 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."); } // Determine whether the class is an adapter for the extended 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 = newConcurrentHashSet<Class<? > > (); wrappers = cachedWrapperClasses; } wrappers.add(clazz); }else { // Get the constructor object by reflection clazz.getConstructor(); // No extension is configured. The extension is automatically generated. For example, DemoFilter is demo, which is mainly used for compatible Java SPI configuration. 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); }}// Get the extension, which can be an array, with multiple extension extensions. String[] names = NAME_SEPARATOR.split(name); if(names ! =null && names.length > 0) { Activate activate = clazz.getAnnotation(Activate.class); // If it is an automatically activated implementation class, it is added to the cache if(activate ! =null) { cachedActivates.put(names[0], activate); } for (String n : names) { if(! cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); }// Cache extension implementation classClass<? > 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()); } } } } } Copy the codeFocus on the IDEA of SPI compatibility with the JDK in this method. Because the SPI related configuration file of JDK is xx.yyy.DemoFilter, there is no key, that is, there is no concept of extension, so for compatibility, the extension generated by xx.yyy.DemoFilter is called Demo.
-
CreateAdaptiveExtensionClass methods: create an adapter class that is similar to dubbo dynamically generated from the $Adpative such classes
privateClass<? > createAdaptiveExtensionClass() {// Create dynamically generated adapter class code String code = createAdaptiveExtensionClassCode(); ClassLoader classLoader = findClassLoader(); com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); // Compile the code to return the class return compiler.compile(code, classLoader); } Copy the code
This method do the logic of the compiled code, code generation in createAdaptiveExtensionClassCode approach, createAdaptiveExtensionClassCode method because of too long, I am not listed here, the following will give you the address of making, Readers can view the relevant source code analysis. CreateAdaptiveExtensionClassCode generated code logic can control the I * * * * in (2) comments @ the Adaptive from $Adpative class to see.
-
Part of the method is simple and does not affect the main function of all I am not listed, this class of other method in the url, please check here to emphasize that the logic is not difficult, difficult is the meaning of the attribute to fully to read to understand, understand the meaning of each attribute, then look at some of the logic is very simple. If you really don’t understand the meaning of a property, you can go to the place where the call is made and use “context” to understand it.
ExtensionLoader class source code address: github.com/CrazyHZM/in…
(6) AdaptiveExtensionFactory
This class is the adapter class for ExtensionFactory, which is the first use of the adapter class I mentioned in the ** (2) annotation @Adaptive. Take a look at the source code for this class:
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
// A collection of extension objects, the default can be divided into dubbo's SPI interface implementation class objects or Spring bean objects
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory(a) {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
// Iterate over all supported extensions
for (String name : loader.getSupportedExtensions()) {
// Add objects to the collection
list.add(loader.getExtension(name));
}
// Returns an unmodifiable collection
factories = Collections.unmodifiableList(list);
}
@Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
// Get the extension object through the extension interface and extension name
T extension = factory.getExtension(type, name);
if(extension ! =null) {
returnextension; }}return null; }}Copy the code
- Factories are collections of extension objects, and when the user doesn’t implement the ExtensionFactory interface himself, there are only two types of objects in this property: SpiExtenfactory and SpringExtensionFactory.
- The constructor adds extension objects with all supported extensions to the collection
- The getExtension method of the interface is implemented to get the extension object by the interface and extension name.
(7) SpiExtensionFactory
SPI ExtensionFactory extension implementation class, look at the source code:
public class SpiExtensionFactory implements ExtensionFactory {
@Override
public <T> T getExtension(Class<T> type, String name) {
// Check whether it is an interface and whether the interface has @spi annotations
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
// Get the extension loader
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if(! loader.getSupportedExtensions().isEmpty()) {// Returns an object of the adapter class
returnloader.getAdaptiveExtension(); }}return null; }}Copy the code
ActivateComparator (eight)
This class is used in the getActivateExtension method of the ExtensionLoader class as a sorter that automatically activates the extension object.
public class ActivateComparator implements Comparator<Object> {
public static final Comparator<Object> COMPARATOR = new ActivateComparator();
@Override
public int compare(Object o1, Object o2) {
// Basic sort
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return -1;
}
if (o2 == null) {
return 1;
}
if (o1.equals(o2)) {
return 0;
}
Activate a1 = o1.getClass().getAnnotation(Activate.class);
Activate a2 = o2.getClass().getAnnotation(Activate.class);
// Sort by using the 'after' and 'before' attributes of the Activate annotation
if ((a1.before().length > 0 || a1.after().length > 0
|| a2.before().length > 0 || a2.after().length > 0)
&& o1.getClass().getInterfaces().length > 0
&& o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) { ExtensionLoader<? > extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
if (a1.before().length > 0 || a1.after().length > 0) {
String n2 = extensionLoader.getExtensionName(o2.getClass());
for (String before : a1.before()) {
if (before.equals(n2)) {
return -1; }}for (String after : a1.after()) {
if (after.equals(n2)) {
return 1; }}}if (a2.before().length > 0 || a2.after().length > 0) {
String n1 = extensionLoader.getExtensionName(o1.getClass());
for (String before : a2.before()) {
if (before.equals(n1)) {
return 1; }}for (String after : a2.after()) {
if (after.equals(n1)) {
return -1; }}}}// Sort by using the 'order' attribute of the Activate annotation.
int n1 = a1 == null ? 0 : a1.order();
int n2 = a2 == null ? 0 : a2.order();
// never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
return n1 > n2 ? 1 : -1; }}Copy the code
The key is to sort by the values in the @Activate annotation.
Afterword.
The source code for this section is github.com/CrazyHZM/in…
This article explains the implementation principle of dubbo’s SPI extension mechanism. The most important thing is to make clear what improvements and optimizations dubbo and JDK have made in the idea of implementing SPI. The most important thing to understand the extension mechanism of Dubbo is to make clear the meanings of @SPI, @Adaptive and @Activate annotations. Most of the logic is encapsulated in ExtensionLoader classes. Many of dubbo’s interfaces are extension interfaces, so reading this article will make it easier for readers to learn about dubbo’s architecture in subsequent articles. If I didn’t write enough or made mistakes in any part, please give me your advice.