Dubbo source code analysis - microkernel plug-in development (SPI introduction)

1 Introduction

Reading good open source frameworks is a shortcut for programmers to progress. Hope you can stick to it. There is a good beginning and a good ending. This part first talks about the core design ideas of dubbo. Microkernel plug-in development. This thought permeates his entire project. Before talking about the microkernel, it may be necessary to talk about the SPI of java. In fact, dubbo has been upgraded based on the SPI design idea.

2. What is SPI

SPI stands for Service Provider Interface. The literal understanding is to provide services for the interface. He is a good interpretation of interface-oriented programming, as well as OCP principles. As we all know, when we are modular programming, the modules are often based on interface programming. For example, module A needs to use a service, but the specific implementation is implemented by a third party. Then A will define an interface for B to implement, but when A instantiates the implementation of this interface, it often needs to hardcode an implementation of B. This will result in that if B changes the implementation, or A wants to use the implementation of C, then the instantiation code of A needs to be modified to support the modification and switching of the implementation. In OCP principles, this modification is to be avoided. So we need an implementation discovery mechanism. Instead of hard-coding an implementation when instantiating, provide an implementation discovery mechanism for the interface, and then move the instantiation function outside of the A module. This idea is very similar to the IOC. When we use Spring, we hand over the assembly of objects to Spring, so that the interface-oriented programming is really achieved between layers.

3. SPI realization idea

Suppose the A module design defines an interface. After B implements this interface, it needs to add a file in the META-INF/services/ directory of B's ​​jar package. The name of this file is the full path name of the interface defined by A, and the content is the full path name of the class that B implements the interface. pathname. At this point, if A needs to use the implementation of B, import the jar package of B. Then A can use java.util.ServiceLoader to discover this implementation and return it to A after instantiating it. In this way, when A uses this implementation, it just uses the defined interface to refer to the implementation, and there is no need to hard-code the implementation. If one day we need to replace the implementation of B, we only need to introduce other third-party jars without modifying the code of A at all.

4. Speak with examples

For example, module A defines an interface as follows:

public interface DemoApi {
    String sayHello(String name);
}

Then B implements this interface as follows:

public class DemoApiImpl1 implements DemoApi {
    public String sayHello(String name) {
        return name + "你好,我是DemoApiImpl1实现";
    }
}

If we don't use the SPI design idea to design the program, then I need to use the DemoApi interface in the A module as follows, this problem comes, when I design the A module, I need to rely on the B module, and someday I want to To replace the implementation of module B, I have to move the code of module A and create a new implementation. In fact, module A only needs to use the services of the DemoApi interface. As for the specific implementation of this interface, you don't need to care at all, let alone rely on a certain implementation in advance.

public static void main(String args[]) {
    DemoApi demoApi = new DemoApiImpl1();
    demoApi.sayHello("xuanner");
}

So, let's improve it and use SPI to encode services that use the DemoApi interface, as follows:

public static void main(String args[]) {
    ServiceLoader<DemoApi> serviceLoader = ServiceLoader.load(DemoApi.class);
    Iterator<DemoApi> iterator = serviceLoader.iterator();
    System.out.println("可以遍历多种可能存在的实现");
    while (iterator.hasNext()) {
        DemoApi demoApi = iterator.next();
        demoApi.sayHello("xuanner")
    }
}

Of course, module B needs to put a configuration file in the META-INF/services/ directory of its JAR package after implementation, as shown in the following figure:

The content inside is the full path of the implementation class, as follows:

com.xuan.spidemo.impl1.DemoApiImpl1

When module A needs the implementation of B, as long as it enters the JAR of B, ServiceLoader will find the implementation of the corresponding interface under the JAR, and then instantiate it and return it to A for use. If one day we need to replace it with C, then as long as After C implements the code, you can also put the above configuration file under his JAR.

5. Examples of existing excellent implementations

The implementation of java.sql.Driver is based on the use of SPI. There is also the commons-logging logging system. There are also implementations based on SPI. I simply excerpted a few relevant fragments, but it seems that he does not use the ServiceLoader class to find the implementation, but implements the loading file by himself.

protected static final String SERVICE_ID = 
    "META-INF/services/org.apache.commons.logging.LogFactory";
if (factory == null) {
            if (isDiagnosticsEnabled()) {
                logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID +
                              "] to define the LogFactory subclass to use...");
            }
            try {
                final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);

                if( is != null ) {
                    // This code is needed by EBCDIC and other strange systems.
                    // It's a fix for bugs reported in xerces
                    BufferedReader rd;
                    try {
                        rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
                    } catch (java.io.UnsupportedEncodingException e) {
                        rd = new BufferedReader(new InputStreamReader(is));
                    }

6. A brief summary

In fact, the design idea of ​​SPI is also very simple. The grounded explanation is that I designed an interface and then did not implement it myself. When the implementation of this interface needs to be used, I use the ServiceLoader class to scan in each dependent third-party package, as long as When a class with an implementation is scanned, it is instantiated to provide services. For me, the implementation is completely transparent, and I only program according to the methods of the interface, which is truly interface-oriented programming. So when we need to change an implementation, we just need to replace the third-party dependent JAR, and my other code doesn't need to be moved. Isn't it great to use this method for third-party extensions. correct.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326099537&siteId=291194637