Dubbo Source Analysis - Dubbo SPI gets extended interface instances through getExtension

Beginning

Four previous articles have analyzed several uses of Dubbo SPI and how it can be used in Dubbo.

This article analyzes how to get an extended object instance through getExtension(name) by debugging the Dubbo2.7.x source code.

text

Review the most basic use of Dubbo SPI

public class App 
{
    public static void main( String[] args )
    {
		// Step One
        ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
        // Step 2
        HelloService helloService = extensionLoader.getExtension("helloService");
        // Step 3
        helloService.sayHello("xiaoming");
    }
}

We go directly from step debug to the getExtension(name) method

1. Properties of ExtensionLoader

Before debug enters the method, let's look at several ExtensionLoader properties

   
    // 1. Extend interfaces, such as Protocol
    private final Class<?> type;
    
    // 2. Extend implementation class collection, key is Protocol, value is DubboProtocol
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
    
    // 3. (Cached) Extended Implementation Class Collection
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

    // 4. Cached extended object collection key is dubbo, value is DubboProtocol
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
 
    // 5. Extend Enhanced Wrapper Implementation Class Collection
    private Set<Class<?>> cachedWrapperClasses;

2. ExtensionLoader # getExtension(String name)

This approach is roughly divided into three steps:

  • If the name is "true", get the default extension class object
  • Otherwise, fetch the cached extension class object
  • If the cache does not exist, load and instantiate the extension class and place it in the cache

Here's an analysis of each of the three steps above

    public T getExtension(String name) {
        
        // Omit extension non-empty check     
        
        // 1. If name equals true, get the default extension object
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        
        // 2. Get from the cached Extension Class object  
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
        
        // There is no corresponding instance in the cache
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                // Dual Check
                if (instance == null) {
                    // 3. Load extension implementation classes and instantiate them
                    instance = createExtension(name);
                    // Extended Object Put in Cache
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

2.1. ExtensionLoader # getDefaultExtension()

   public T getDefaultExtension() {
        // 1.1 Get all extension classes
        getExtensionClasses();
        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
            return null;
        }
        return getExtension(cachedDefaultName);
    }
2.1.1 getExtensionClasses()
   private Map<String, Class<?>> getExtensionClasses() {
        // Fetch from cache first
        // cachedClasses places a collection of cached extended implementation classes
        Map<String, Class<?>> classes = cachedClasses.get();
        // Still a dual check + lock method to get
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // If the cache does not hit, it will load META-INF/dubbo, META-INF/dubbo/intenal, META-INF/service directory.
                    classes = loadExtensionClasses();
                    // When loading is complete, put into the cache
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
2.1.2 loadExtensionClasses()

As mentioned above, if you don't have one in the cache, you'll go to the directory below to load the file and parse the contents.

  • META-INF/dubbo
  • META-INF/dubbo/intenal
  • META-INF/service

The specific source code is not pasted out, the content format of the file is as follows:

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

2.2. ExtensionLoader # getOrCreateHolder()

    private Holder<Object> getOrCreateHolder(String name) {
        // As above, fetch from the cache first.    
        // cachedInstances places cached extension class objects
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }

2.3 ExtensionLoader # createExtension(String name)

  • Get the extended implementation class (DubboProtocol) by name (for example, dubbo)
  • Get Extended Implementation Class Object instance from Extended Implementation Class Collection
  • If the extension class object instance relies on other extensions to implement the class OtherClass, you need to instantiate OtherClass and inject it into the instance through the setter method
  • Determine if Protocol has other enhancements, such as ProtocolFilterWrapper, and so on
    • If so, instantiate it ProtocolFilterWrapper and assign it to instance
    
    private T createExtension(String name) {
        // Gets the specified extension class
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        
        try {
            // Get from the collection of extended implementation classes
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // Injection Dependent Extended Class Objects
            injectExtension(instance);
            // Determine whether it is wrapper
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    // Re-assign to a wrapper (Enhanced Class)
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

summary

This article mainly aims at obtaining an extended object instance through getExtension(name) to analyze the source code of Dubbo. Of course, the full text only describes a general process, such as how to parse the SPI configuration file without in-depth explanation.

  • Determines if the acquired extension object is the default ("true".equals(name))

    • true, getDefaultExtension(); method gets default extension class object
    • false to determine if there is a cache cachedInstances collection
      • If so, get it from the cached extension class object
      • If not, get the instantiated object through createExtension and put it in the cache
        • Get extended implementation classes such as DubboProtocol
        • Determine if there is an object instance in the cache that extends the implementation class, and if not, instantiate the class DubboProtocol from the previous step
        • Determine whether instance s depend on other extension class objects CLassA, CLassB, and so on, and if so, need to be injected through the setter method
        • Determine if DubboProtocol has additional Wrapper implementation classes, such as ProtocolFilterWrapper, and if so, assign them to the above instance
  • Return extended class object return instance

The next article will do source analysis for the Get Adaptive Extension () method for an adaptive extension point instance!

Tags: Programming Dubbo Apache

Posted on Sat, 28 Mar 2020 09:31:38 -0700 by rem