The Dubbo source code series has four chapters for now, SPI, Service Exposure, service reference, and service invocation procedure. When I was studying, I read the official document. For the first time, I saw such a thoughtful Chinese document and a very detailed source code analysis, which made me feel good about Dubbo +1. Although I have seen the official document, there are also some of my own understanding and deletion, welcome to read ~

SPI is called Service Provider Interface. Its function is to write the fully qualified name of the interface implementation class into the configuration file of the specified directory, so that the framework can read the configuration file and load the implementation class. This allows us to dynamically replace implementation classes for interfaces, making the framework more extensible. Java actually has a native SPI mechanism, but Dubbo doesn’t use it. The premise of learning Dubbo source code is to understand Dubbo SPI mechanism.

0. Java SPI example

public interface Hello{
    void sayHello(a);
}
Copy the code
public class testA implements Hello{
    @Override
    public void sayHello(a){
        System.out.println("Hello,I am A"); }}public class testB implements Hello{
    @Override
    public void sayHello(a){
        System.out.println("Hello,I am B"); }}Copy the code

Once the implementation class and interface are written, we need to create a new file in the meta-INF /services directory with the fully qualified name of interface Hello. Then write the fully qualified names of all implementation classes in the file, for example:

com.yelow.spi.testA 
com.yelow.spi.testB
Copy the code

test

public class JavaSPITest{
    @Test
    public void sayHello(a)  throws Exception{
        ServiceLoader<Hello> serviceLoader = ServiceLoader.load(Hello.class);
        serviceLoader.forEach(Hello::sayHello);
        // Output:
        //Hello,I am A
        //Hello,I am B}}Copy the code

1. The Dubbo SPI example

Dubbo SPI is similar to Java SPI in usage. You define the interface and implementation class, with the @spi annotation in front of the interface to represent an extension point. Create another configuration file. But the file’s path should be in meta-INF /dubbo/. Configuration file contents should be in key-value pair form, for example:

testA = com.yelow.spi.testA 
testB = com.yelow.spi.testB
Copy the code

The final test method is as follows:

public class JavaSPITest{
    @Test
    public void sayHello(a)  throws Exception{
        ExtensionLoader<Hello> loader=ExtensionLoader.getExtensionLoader(Hello.class);
        // Load as required. The parameter is the key value in the configuration file
        Hello testA=loader.getExtension("testA");
        testA.sayHello();
        // Output Hello,I am A}}Copy the code

2.Dubbo SPI source analysis

This is a simple example of how to use Dubbo. Pass ExtensionLoader. GetExtensionLoader ExtensionLoader object. Get the implementation class object through this object’s getExtension method. Take a look at the getExtensionLoader method:

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!");
    }
    if(! withExtensionAnnotation(type)) {throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    // Try to get the ExtensionLoader object from the local cache
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    // If there is no cache, create a new one
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}
Copy the code

The getExtensionLoader method is simpler, so let’s look at getExtension:

public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) {
        // Get the default implementation class
        return getDefaultExtension();
    }
    // This class is used to hold the target object, first retrieved from the cache
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    // Get the instance object
    Object instance = holder.get();
    // If not, create a new instance object. This is a double check. For the significance, see singleton mode
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // Create an implementation instance object
                instance = createExtension(name);
                // Assign to holderholder.set(instance); }}}return (T) instance;
}
Copy the code

Similarly, check the cache first and create a new one if there is no cache. Let’s see how to create a new instance object by going to the createExtension method:

private T createExtension(String name) {
    // Get all implementation classes from the configuration fileClass<? > clazz = getExtensionClasses().get(name);if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            // Create an instance through reflection
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // Inject dependencies into instance objectsinjectExtension(instance); Set<Class<? >> wrapperClasses = cachedWrapperClasses;if(wrapperClasses ! =null && wrapperClasses.size() > 0) {
            for(Class<? > wrapperClass : wrapperClasses) {// Pass the current instance object as an argument to the Wrapper constructor and create the Wrapper object through reflection
                // Inject dependencies into the Wrapper instance object, and finally assign the Wrapper to instanceinstance = 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

In the above method, the line of code that assigns instance is a bit more complicated, but the ultimate goal is to wrap the implementation class object in a Wrapper object. As you can see from the comments above, the purpose of the createExtension method is four. Focus on getExtensionClasses and injectExtension methods:

privateMap<String, Class<? >> getExtensionClasses() {// Get the loaded implementation class from the cacheMap<String, Class<? >> classes = cachedClasses.get();// Double check
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // Load the implementation classclasses = loadExtensionClasses(); cachedClasses.set(classes); }}}return classes;
}
Copy the code

Check the cache first, and then create a new one if there is no cache. We enter the loadExtensionClasses method:

privateMap<String, Class<? >> loadExtensionClasses() {// Get SPI annotations
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation ! =null) {
        String value = defaultAnnotation.value();
        if(value ! =null && (value = value.trim()).length() > 0) {
            // Split the values of SPI annotations
            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));
            }
            // Set the default name
            if (names.length == 1) cachedDefaultName = names[0]; } } Map<String, Class<? >> extensionClasses =newHashMap<String, Class<? > > ();// Load the configuration file in the specified folder
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}
Copy the code

LoadFile = loadFile (); loadFile = loadFile ();

As you can see, the directory mentioned in the previous example is specified here. While meta-INF /services/ is for Compatibility with Java SPIs, internal/ is Dubbo’s own extended class configuration file. Finally, let’s examine the loadFile method:

private void loadFile(Map
       
        > extensionClasses, String dir)
       ,> {
    // Folder path + fully qualified interface name = Specific configuration file path
    String fileName = dir + type.getName();
    try {
        Enumeration<java.net.URL> urls;
        ClassLoader classLoader = findClassLoader();
        // Load all files with the same name according to the filename
        if(classLoader ! =null) {
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        // Get the file and read the configuration file
        if(urls ! =null) {
            while (urls.hasMoreElements()) {
                java.net.URL url = urls.nextElement();
                try {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                    try {
                        String line = null;
                        // Read by line
                        while((line = reader.readLine()) ! =null) {
                            // Parse the configuration file
                            // Load the implementation class through reflection
                            // Operation cache
                            //... It is best to debug yourself, the most clear
                        } // end of while read lines
                    } finally{ reader.close(); }}catch (Throwable t) {
                    logger.error("Exception when load extension class(interface: " +
                            type + ", class file: " + url + ") in "+ url, t); }}// end of while urls}}catch (Throwable t) {
        logger.error("Exception when load extension class(interface: " +
                type + ", description file: " + fileName + ").", t); }}Copy the code

Now that we’re done getting the source code analysis of the implementation class, let’s go back to the createExtension method and look at injectExtension, Dubbo’s dependency injection function. Dubbo IOC is about injecting dependencies through setter methods. It gets the instance’s list of methods by reflection, then iterates to see if the method has the characteristics of setter methods, and if so, it calls that setter method by reflection to set the dependency on the target object. Code analysis is as follows:

private T injectExtension(T instance) {
    try {
        if(objectFactory ! =null) {
            // the traversal method
            for (Method method : instance.getClass().getMethods()) {
                // Determine if the method starts with set, has only one argument, and the method is public
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                            // Get setter method parameter typesClass<? > pt = method.getParameterTypes()[0];
                    try {
                        // Get the familiar name, such as setName, whose corresponding property should be name
                        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) {
                            // Call setter methods via reflection to do dependency injectionmethod.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