SPI Principle of dubbo Source Analysis 1 and IOC and AOP Principles

1. What is Dubbo SPI

First, look at the description of the dubbo website:
The SPI is called Service Provider Interface and is a service discovery mechanism.The essence of SPI is to configure the fully qualified name of the interface implementation class in a file and load the implementation class by reading the configuration file from the service loader.This dynamically replaces the implementation class for the interface at runtime.Because of this feature, we can easily extend our programs through the SPI mechanism.SPI mechanisms are also used in third-party frameworks, such as Dubbo, where all components are loaded through the SPI mechanism.However, instead of using Java's native SPI mechanism, Dubbo has enhanced it to better meet demand.In Dubbo, SPI is a very important module.Based on SPI, we can easily expand Dubbo.If you want to learn the source code of Dubbo, the SPI mechanism must be understood.

2.Java SPI and Dubbo SPI examples

Referring to an example from the official website, here are two implementations of the Robot interface, Optimus Prime and Bumblebee.

@SPI   //JavaSPI does not require this comment, it is required when using dubboSPI
public interface Robot {
    void sayHello();
}

public class OptimusPrimeimplements Robot {
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebeeimplements Robot {
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

The Java SPI configuration file is placed under META-INF/services as follows:

com.pigcoffe.spi.Bumblebee
com.pigcoffe.spi.OptimusPrime

The configuration file for the Dubbo SPI is placed under META-INF/dubbo as follows:

optimusPrime = com.pigcoffe.spi.OptimusPrime
bumblebee = com.pigcoffe.spi.Bumblebee

File names are interface class names.
Java SPI test class:

package com.pigcoffe.spi;
import com.pigcoffe.spi.Robot;
import org.junit.Test;
import java.util.ServiceLoader;
public class JavaSPITest {
    @Test
    public void sayHello() throws Exception {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(Robot::sayHello);
    }
}

Output:

Java SPI
Hello, I am Bumblebee.
Hello, I am Optimus Prime.

Dubbo SPI test class, where dubbo dependencies need to be introduced

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.4</version>
        </dependency>
package com.pigcoffe.spi;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import org.junit.Test;
public class DubboSPITest {
    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader =
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}

Output:

Hello, I am Optimus Prime.
Hello, I am Bumblebee.

3. What are the advantages of Dubbo SPI? Why not use the SPI that comes with jdk?

You can see some differences in the sample code

  • The JDK standard SPI instantiates all the extensions point implementations at once, which can be time consuming to initialize but wasteful to load if not used.
  • If the extension point fails to load, the extension point name is not even available.For example, the JDK standard ScriptEngine gets the name of the script type through getName(), but if RubyScriptEngine fails to load the RubyScriptEngine class because the dependent jruby.jar does not exist, the reason for the failure is eaten up and does not correspond to ruby. When a user executes a ruby script, ruby is not supported, not the real reason for the failure.
  • JDK SPI does not support caching, defaults, IOC and AOP functionality

4. Source Code Analysis

Start with the first line of code

ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");

From extensionLoader.getExtension("optimusPrime"); follow-up code

    public T getExtension(String name) {
        if (name != null && name.length() != 0) {
            if ("true".equals(name)) {
                return this.getDefaultExtension();
            } else {
                Holder<Object> holder = (Holder)this.cachedInstances.get(name);
                if (holder == null) {
                    this.cachedInstances.putIfAbsent(name, new Holder());
                    holder = (Holder)this.cachedInstances.get(name);
                }

                Object instance = holder.get();
                if (instance == null) {
                    synchronized(holder) {
                        instance = holder.get();
                        if (instance == null) {
                            //Not in cache, newly created
                            instance = this.createExtension(name);
                            holder.set(instance);
                        }
                    }
                }

                return instance;
            }
        } else {
            throw new IllegalArgumentException("Extension name == null");
        }
    }

The logic is simple: first get the object from the cache, and if not create this.createExtension(name) and join the cache
The following is the createExtension(String name) method

    private T createExtension(String name) {
//Load all the extended classes from the configuration file to get a mapping table from Configuration Item Name to Configuration Class
        Class<?> clazz = (Class)this.getExtensionClasses().get(name);
        if (clazz == null) {
            throw this.findException(name);
        } else {
            try {
                T instance = EXTENSION_INSTANCES.get(clazz);
                if (instance == null) {
                    EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                    instance = EXTENSION_INSTANCES.get(clazz);
                }
                //dubbo IOC
                this.injectExtension(instance);
                Set<Class<?>> wrapperClasses = this.cachedWrapperClasses;
                Class wrapperClass;
                if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                    for(Iterator i$ = wrapperClasses.iterator(); i$.hasNext(); 
//dubbo AOP
instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) {
                        wrapperClass = (Class)i$.next();
                    }
                }

                return instance;
            } catch (Throwable var7) {
                throw new IllegalStateException("Extension instance(name: " + name + ", class: " + this.type + ")  could not be instantiated: " + var7.getMessage(), var7);
            }
        }
    }

This code starts with getExtensionClasses() loading all extension classes from the configuration file to get a mapping table from Configuration Item Name to Configuration Class.
ExtensionLoader#getExtensionClasses()–>loadExtensionClasses()

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/";
  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;
    }

This reflects the dubbo SPI configuration file directory. In addition to META-INF/dubbo/, there are META-INF/dubbo/internal/. The main extensions placed in the internal directory are the Dubbo framework's own extensions. User-created extensions are generally placed in the Dubbo directory. The configuration file of the above example can also run normally if moved to the META-INF/dubbo/internal/directory.

Then it involves the IOC and AOP implementations of dubbo, advanced to the implementation of this.injectExtension(instance) IOC

    private T injectExtension(T instance) {
        try {
            if (this.objectFactory != null) {
                Method[] arr$ = instance.getClass().getMethods();
                int len$ = arr$.length;

                for(int i$ = 0; i$ < len$; ++i$) {
                    Method method = arr$[i$];
                    if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) {
                        Class pt = method.getParameterTypes()[0];

                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = this.objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception var9) {
                            logger.error("fail to inject via method " + method.getName() + " of interface " + this.type.getName() + ": " + var9.getMessage(), var9);
                        }
                    }
                }
            }
        } catch (Exception var10) {
            logger.error(var10.getMessage(), var10);
        }

        return instance;
    }

Here, the setter method injection dependent objects are found by reflection acquisition method traversal. Dependent objects are obtained by objectFactory. There are two SpiExtension Factories and Spring Extension Factory to get an object

The implementation of AOP is in this line of code:

instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));

Here, the wapperClass is actually a class that has a constructor whose parameter is the target interface. This uses the decorator design mode. For example, with the Robert interface above, create a new RobotCheckWapper class to implement aop functionality, and then check to see if it carries weapons before the robot says helle, as follows

package com.pigcoffe.spi;

import com.alibaba.dubbo.common.extension.Adaptive;


public class RobotCheckWapper implements Robot {
    private final Robot robot;
    public RobotCheckWapper(Robot robot){
        this.robot = robot;
    }

    @Override
    public void sayHello() {
        System.out.println("--->say hello Previously checked for weapons ---->");
        robot.sayHello();
    }
}

Add a line to the configuration file:
optimusPrime = com.pigcoffe.spi.OptimusPrime
bumblebee = com.pigcoffe.spi.Bumblebee
bumblebeeWapper = com.pigcoffe.spi.RobotCheckWapper

Print the ubboSPITest test class before running as follows:

--->say hello Previously checked for weapons ---->
Hello, I am Optimus Prime.
--->say hello Previously checked for weapons ---->
Hello, I am Bumblebee.

Whether or not the AOP functionality has been completed is similar to logging or time statistics.

5. Summary

This paper introduces what is SPI, what is Dubbo SPI, and its basic use.
The advantages of dubbo SPI over jdk SPI are compared.
The implementation principle of dubbo IOC and AOP is briefly introduced.
The next article introduces dubbo's extension mechanism, the @Adaptie comment, and the @Active comment

52 original articles published. 8% praised. 130,000 visits+
Private letter follow

Tags: Dubbo Java JDK Ruby

Posted on Sat, 11 Jan 2020 16:41:54 -0800 by sfmnetsys