preface

Dubbo is a distributed, high-performance, transparent RPC services framework designed to provide efficient service governance solutions such as automatic service registration, automatic discovery, etc., so I decided to take a look at the source code to see why.

SPI

A simple example:

/ / interface
public interface TestService {
    String getName(a);
}
/ / implementation class
public class TestServiceImpl implements TestService {
    @Override
    public String getName(a) {
        return "test"; }}Copy the code

Create a file ++ in the resources/ meta-INF /services directory with the same name as the ++ interface path

File name: xx.xx.xx.testService

The file content is xx.xx.xx.TestServiceImpl

public static void main(String[] args) {
        ServiceLoader<DriverService> serviceLoader = ServiceLoader.load(TestService.class);
        for(TestService testService: serviceLoader){ System.out.println(testService.getName()); }}Copy the code

Depending on the implementation class configured in the file, the above code outputs test, which has the advantage of dynamically substituting different implementation classes

The core idea is somewhat similar to the policy pattern, which has an implementation method: write an interface and several different implementation classes, put the implementation classes into a HashMap, and then call different implementation classes based on different keys.

While dynamic substitution of implementation classes is possible, JAVA’s built-in SPI has a number of disadvantages:

  • Cannot load on demand. Although ServiceLoader does lazy loading, it is basically only possible to fetch it all by traversing it, i.e. the implementation classes of the interface are loaded and instantiated once. If you don’t want to use some implementation class, or if instantiation of some class is time consuming, it gets loaded and instantiated, and that’s a waste.
  • The method of obtaining an implementation class is not flexible. The method can only be obtained in the form of Iterator. The corresponding implementation class cannot be obtained according to a parameter.
  • It is not safe for multiple concurrent threads to use instances of the ServiceLoader class.
  • When an implementation class is not loaded, an exception is thrown for no real reason, and the error is difficult to locate.

Dubbo-SPI

First download dubbo source code: source address. There is a demo project in the source code, all of the following code is written in the Demo project

Dubbo-spi and SPI are the same idea, but the implementation details are different. Take a look at the sample code:

@SPI("test")
public interface SpiTest {

    String getName(a);

    @Adaptive
    int getAge(URL url);

    @Adaptive(value = "country")
    String getCountry(URL url);

    @Adaptive({"province", "city"})
    String getAddress(URL url);
}
Copy the code

This is an interface marked with the SPI tag

public class SpiImpl implements SpiTest{
    @Override
    public String getName(a) {
        return "test";
    }

    @Override
    public int getAge(URL url) {
        return 123;
    }

    @Override
    public String getCountry(URL url) {
        return "GuangZhou";
    }

    @Override
    public String getAddress(URL url) {
        return "China"; }}Copy the code

The implementation class

Then create the file under resouces

The file content: test = org. Apache. Dubbo. Demo. The provider. SpiImpl

// Test the code
    public static void main(String[] args) {
        ExtensionLoader<SpiTest> loader = ExtensionLoader.getExtensionLoader(SpiTest.class);
        SpiTest spiTest = loader.getAdaptiveExtension();
        URL url = URL.valueOf("? country=test");
        System.out.println(spiTest.getCountry(url));
    }
Copy the code

Running the code above will bring up: GuangZhou

Let’s first explain the two annotations used above:

  • @SPI(“test”)

SPI is a tag, and the test value corresponding to the test = org. Apache. Dubbo. Demo. The provider. SpiImpl, attention to the bold, and said the implementation class name; This value does not enforce the use of the test implementation class, but is a default that is used if no other implementation class is available

  • @Adaptive(value = “country”)

Note the parameters to this URL: country URL URL = url.valueof (“? country=test”); Each SPI method must construct a URL parameter, with country representing the parameter in the URL

If you write an implementation class: test1 = org. Apache. Dubbo. Demo. The provider. SpiImpl1

Url url = url.valueof (“? Country =test1”) then the SpiImpl1 implementation method will be executed


Summarize the meaning of these two annotations and parameters based on multiple tests and documentation:

  • @SPI

This annotation can be applied to classes, interfaces, and enumerated classes, all on interfaces in the Dubbo framework, indicating that the interface is a Dubbo SPI interface, meaning an adaptive extension point. The internal argument is used to specify the default value, which is the default call when no implementation class is specified.

  • @Adaptive

This annotation can be applied to classes, interfaces, enumerated classes, and methods, mostly methods in Dubbo. – For classes: When this interface is applied to a class, it means that the class directly applies to the default implementation. All implementation classes must have only one @Adaptive annotation, otherwise an exception will be thrown. – value parameter: The value parameter is an array. It is used to match the key parsed from the URL. If no match is found, the hump rule is used.


Read the source code according to the use process

Take a look at ExtensionLoader

Several directory addresses:

“META-INF/services/”

“META-INF/dubbo/”

“META-INF/dubbo/internal/”

Represents the static file storage path, you need to put these paths to find dubbo

What’s left is a bunch of caches, so let’s look at the main method

The previous section verifies that the class passed in meets the criteria: the interface type and the @SPI annotation, and then tries to pull the ExtensionLoader out of the cache, or create a new one if it doesn’t

According to the SpiTest SpiTest = loader. GetAdaptiveExtension (); Take a look at the getAdaptiveExtension() method

Synchronized +double-check, first check whether it has been loaded, after the cache double check, not to create.

The most main or createAdaptiveExtensionClassCode this method

This pile of code is bytecode edited into StringBuilder and then compiled by Compiler, which also uses SPI technology. There are two main compilation techniques: JDK and Javassist, Dubbo uses an adaptive piler fixed implementation by default, using JavAssist as the default compiler.

Directly after see createAdaptiveExtensionClassCode compiled code:

The getName method parameter does not have a URL, so it cannot be called

The Adaptive parameters are resolved, and then the getExtension method of the ExtensionLoader is called to retrieve the implementation class.

As you can see, ExtensionLoader uses the template to wrap the original interface and determine the URL parameters, and then calls getExtension to get the actual implementation class based on the parameter values.

Take a look at the getExtension code

Also check the cache and create it if it doesn’t exist

Look at the createExtension method

The first call goes into the getExtensionClasses method, and then into the method at the beginning of injectExtension Reflection Set to inject parameters.

Check the cache and enter loadExtensionClasses if not

If there are any parameters on the SPI, use the first parameter of the array as the default name

Get the file URL from the urls = classLoader.getResources(fileName) method and continue down loadResource

Separate the key and value, use class.forname to load, and then continue down loadClass

Some determination of the class is then put into the cache, at which point all the classes configured in the mate-INF file are loaded into memory.

Note the isWrapperClass(clazz) method

The Wrapper class in the constructor will put the class into a Set, similar to the AOP Wrapper. This is the easy AOP of SPI.

Specific examples:

public class SpiTestImplWrapper implements SpiTest {

    private final SpiTest test;

    public TestServiceImplWrapper(SpiTest test){
	this.test = test;
    }
    @Override
    public String getName(a) {
        return "wrapper:"+testService.getName(); }... Other methods}Copy the code

When TestServiceImplWrapper is found, the system wraps the original TestServiceImpl into TestServiceImplWrapper. AOP – like multi-level nesting.

Wrappers are used in many places in Dubbo


Dubbo-spi is a foundation of Dubbo, which can be found everywhere in the source code. Understanding the principle of SPI helps to read the future code.

Dubo-spi Flowchart:

Stage flow chart

Getting an interface instance

A method is called