The SPI mechanism of Java is described above, but it cannot implement on-demand loading. It loads all the implementation classes of the interface. Therefore, Dubbo does not use Java SPI, but implements a set of SPI mechanism by himself, encapsulates the logic in ExtensionLoader, and implements on-demand loading

Dubbo spi sample

Create the interface first:

@SPI // Note that this annotation is required
public interface SPIService {
    void sayHello(a);
}
Copy the code

Create an interface implementation class:

public class SPIServiceImplOne implements SPIService {

    @Override
    public void sayHello(a) {
        System.out.println("Hello, this is implementation class 1."); }}public class SPIServiceImplTwo implements SPIService {
    @Override
    public void sayHello(a) {
        System.out.println("Hello, this is implementation class 2."); }}Copy the code

Create dubbo folder under meta-INF of resource and create file with fully qualified interface name:

META-INF/dubbo/com.lgx.dubbo.spi.SPIService

SPIServiceImplOne=com.lgx.dubbo.spi.impl.SPIServiceImplOne
SPIServiceImplTwo=com.lgx.dubbo.spi.impl.SPIServiceImplTwo
Copy the code

Next test:

@Test
public void testDubboSPI(a){
    /** * Get ExtensionLoader (the mapping from class to get ExtensionLoader is saved in the map) */
    ExtensionLoader<SPIService> extensionLoader = ExtensionLoader.getExtensionLoader(SPIService.class);
    /** * Create extension point * 1. Use getExtensionClasses to get all extension classes in the specified directory (the mapping from config item names to config classes is saved in the map) * 2. Create extension objects by reflection clazz.newinstance * 3. Inject dependencies into the extension object. 4. Wrap the extension object in the corresponding Wrapper object AOP */
    SPIService loaderOne = extensionLoader.getExtension("SPIServiceImplOne");
    loaderOne.sayHello();
    SPIService loaderTwo = extensionLoader.getExtension("SPIServiceImplTwo"); loaderTwo.sayHello(); } Output: Hello, I am the implementation class1Hello, THIS is the implementation class2
Copy the code

The implementation principle of Dubbo SPI

Look at the first line:

ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(SPIService.class);

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    // Whether it is an interface
    if(! type.isInterface()) {throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    // Whether the @spi annotation is standard on the interface
    if(! withExtensionAnnotation(type)) {throw new IllegalArgumentException("Extension type (" + type +
                ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }
	// ConcurrentMap
      
       , ExtensionLoader
       > EXTENSION_LOADERS = new ConcurrentHashMap<>() from the cache
      >
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        // Create a new one and add it to the cache
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}
private ExtensionLoader(Class
        type) {
        this.type = type;
    // Ignore the adaptive extension here
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
Copy the code

Moving on to this line:

SPIService loaderOne = extensionLoader.getExtension(“SPIServiceImplOne”);

public T getExtension(String name) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    // Get the default Extension
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    // Use singleton double search
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // Create an instanceinstance = createExtension(name); holder.set(instance); }}}return (T) instance;
}

private T createExtension(String name) {
    	// Load all extension classes from the configuration file to get the mapping from "config item name" to "config class"Class<? > clazz = getExtensionClasses().get(name);if (clazz == null) {
            throw findException(name);
        }
        try {
            // Fetch from the cache
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
               // Failed to get the created instance and added it to the cache
               / / map < class com. LGX. Dubbo. Spi. Impl. SPIServiceImplTwo, SPIServiceImplTwo instance >
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // dependency injection
            injectExtension(instance);
            // Object wrappingSet<Class<? >> wrapperClasses = cachedWrapperClasses;if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                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 + ") couldn't be instantiated: "+ t.getMessage(), t); }}Copy the code

CreateExtension is divided into four steps:

  1. <name,class> <name,class>

  2. Clazz.newinstance () creates an instance to add to the cache

  3. Dependency injection

  4. Wrap the created object into a Wrapper instance

Take a look at the first step:

// Get the fully qualified name of the implementation class and add it to the map to form the map
privateMap<String, Class<? >> getExtensionClasses() {// From the cacheMap<String, Class<? >> classes = cachedClasses.get();// Double search
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // Load the interface file to get the Class of the implementation Classclasses = loadExtensionClasses(); cachedClasses.set(classes); }}}return classes;
}

privateMap<String, Class<? >> loadExtensionClasses() { cacheDefaultExtensionName(); Map<String, Class<? >> extensionClasses =new HashMap<>();
    	// DUBBO_INTERNAL_DIRECTORY = META-INF/dubbo/internal/
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
    	// DUBBO_INTERNAL_DIRECTORY = META-INF/dubbo/
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
    	// DUBBO_INTERNAL_DIRECTORY = META-INF/services/
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache"."com.alibaba"));
        return extensionClasses;
}
Copy the code

We created it in the meta-INF /dubbo/ directory so we’ll load files from that directory

private void loadDirectory(Map
       
        > extensionClasses, String dir, String type)
       ,> {
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls;
        // Get ClassLoader ==> AppClassLoader
        ClassLoader classLoader = findClassLoader();
        if(classLoader ! =null) {
            // Load the file
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        if(urls ! =null) {
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                // The implementation Class name of each line and the corresponding Class are put into the map to form a maploadResource(extensionClasses, classLoader, resourceURL); }}}catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                type + ", description file: " + fileName + ").", t); }}// Load resources
private void loadResource(Map
       
        > extensionClasses, ClassLoader classLoader, java.net.URL resourceURL)
       ,> {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                while((line = reader.readLine()) ! =null) {
                    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) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                // Go here, load the class and add the cache
                                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);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in "+ resourceURL, t); }}private void loadClass(Map
       
        > extensionClasses, java.net.URL resourceURL, Class
         clazz, String name)
       ,> throws NoSuchMethodException {
        if(! type.isAssignableFrom(clazz)) {throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
    	// Forget about adaptive extension
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
        } else if (isWrapperClass(clazz)) { // Whether it is Wrapper type
            cacheWrapperClass(clazz);
        } else {
            // Ordinary extension classes
            // Tests whether Clazz has a default constructor and throws an exception if it does not
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                // If name is empty, name is taken from the Extension annotation
                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 (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    // Add the mapping to the cachecacheName(clazz, n); saveInExtensionClass(extensionClasses, clazz, n); }}}}Copy the code

Step 2 is easier. Let’s look at Step 3:

private T injectExtension(T instance) {
    try {
        if(objectFactory ! =null) {
            // Iterate over all methods of the instance
            for (Method method : instance.getClass().getMethods()) {
                // Whether it is set
                if (isSetter(method)) {
                    // Whether the automatic injection method requires the DisableInject annotation
                    if(method.getAnnotation(DisableInject.class) ! =null) {
                        continue; } Class<? > pt = method.getParameterTypes()[0];
                    // Check whether the parameter is a primitive type
                    if (ReflectUtils.isPrimitives(pt)) {
                        continue;
                    }
                    try {
                        // Get the property name, such as setPerson
                        String property = getSetterProperty(method);
                        // Call the getExtension method of SpiExtensionFactory
                        Object object = objectFactory.getExtension(pt, property);
                        if(object ! =null) {
                            // Execute this method through reflection to implement injectionmethod.invoke(instance, object); }}catch (Exception e) {
                        logger.error("Failed to inject via method " + method.getName()
                                + " of interface " + type.getName() + ":" + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}
public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        // Whether it is an interface with SPI annotations on the interface
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if(! loader.getSupportedExtensions().isEmpty()) {// Get adaptive extensions
                returnloader.getAdaptiveExtension(); }}return null; }}Copy the code

Step four is ignored for now

conclusion

The SPI mechanism of Dubbo is also relatively simple. The implementation class name is passed to the cache to find if there is a corresponding instance, and if there is no instance, create it. META-INF/dubbo/internal/ META-INF/services/META-INF/dubbo/internal/ META-INF/services Add the Class to the cache, get the corresponding Class according to the name of the implementation Class passed in, and finally instantiate the implementation Class instance (add IOC->setter dependency injection, AOP->Wrapper instance, and have a lot of cache).