SPI mechanism of Java Service Provider Interface

SPI stands for Service Provider Interface, which is a set of interfaces provided by Java to be implemented or extended by third parties. It can be used to enable framework extensions and replace components. The role of SPI is to find service implementations for these extended APIs. A classic SPI implementation in Java is the JDBC Driver loading mechanism. When we first learned JDBC, we needed to use Class.forName("com.mysql.jdbc.Driver") to load the database driver. In fact, we can omit this step in the latest version of the JDK, which has been loaded in the JDK through SPI. There is a static block in the DriverManager class for loading the database driver, the code is as follows:

static {
    //加载并初始化驱动
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

Here we analyze the loadInitialDrivers(); method in detail. The main logic of this method is two parts. One part is to obtain the full class name of the driver from the system property jdbc.drivers, and then load and initialize. The second is to use the SPI mechanism to load the driver class. . The code is as follows:

private static void loadInitialDrivers() {
    
    //从系统属性jdbc.drivers获取驱动的全类名,代码不再展示
       
    //通过SPI机制加载初始化驱动
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            //通过ServiceLoader加载驱动
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                //初始化实例化驱动
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
                // Do nothing
            }
                return null;
        }
    });
   ......
   //加载实例化从系统属性获取的驱动,代码不再展示
}

As in the above code, we mainly focus on loading the driver through ServiceLoader, which is the so-called SPI mechanism. ServiceLoader loads the file with the full interface name under the META-INF/services directory, and the content of the file is the implementation class of the interface. For example, in our mysql-connection.jar, there is a file named java.sql.Driver in its META-INF/services directory, the contents of which are com.mysql.jdbc.Driver and com.mysql.fabric.jdbc. Driver. Below we introduce how he loads and initializes these two instances. In the above method, there is a driversIterator.hasNext() method. The source code is as follows:

public boolean hasNext() {
    if (acc == null) {
        //调用hasNextService()方法
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            //调用hasNextService()方法
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}

private boolean hasNextService() {
    if (nextName != null) {
       return true;
    }
    if (configs == null) {
       try {
           //加载META-INF/service下的名称为接口全名称的文件
           String fullName = PREFIX + service.getName();
           if (loader == null)
               configs = ClassLoader.getSystemResources(fullName);
           else
                        configs = loader.getResources(fullName);
           } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
           }
      }
    //其他逻辑,解析文件
           
}

The above method loads a file whose name is META-INF/services as the full name of the interface. Is the subsequent logic to parse the file, and then initialize and instantiate the implementation class configured in the file. We will not introduce the following code. Let's use ServiceLoader as an example. As follows, we create an interface SPIService and two implementation classes. The code is as follows:

public interface SPIService {
    void printSpiService();
}
public class CustomizedSPIService implements SPIService {
    public void printSpiService() {
        System.out.println("I am CustomizedSPIService");
    }
}
public class MicroserviceSPIService implements SPIService {
    public void printSpiService() {
        System.out.println("I am MicroserviceSPIService");
    }
}

Below we do not use new to instantiate the above two implementation classes, but to load them through ServiceLoader, because the implementation class implements SPIService, so we first create a name under META-INF/services named cn.org.microservice. inter.SPIService file, and write the full class names of the two implementation classes, as shown below:

 

 The following is the test code, which will call the methods of two implementation classes, and then print out the content:

public class ServiceLoaderAp {
    public static void main(String[] args) {
        ServiceLoader<SPIService> services = ServiceLoader.load(SPIService.class);
        services.forEach((spiService) ->{
            spiService.printSpiService();
        });
    }
}

Java's SPI mechanism allows us to extend our application without changing the source code package, but it also has some shortcomings, such as the inability to containerize, that is, the inability to hand over instantiated objects to the container for management. Therefore, many frameworks are custom SPI mechanisms, such as spring boot and Alibaba's open source dubbo. Below we will introduce these two SPI mechanisms. First, we introduce Dubbo's SPI mechanism.

Dubbo's SPI related logic is encapsulated in the ExtensionLoader class, through the ExtensionLoader we can load the specified implementation class. Dubbo will load files named after the full path name of the interface from the META-INF/services, META-INF/dubbo, and META-INF/dubbo/internal directories. Unlike the JDK built-in SPI mechanism, dubbo provides key-value keys. The right way. Let's take the above interface as an example. We create a dubbo directory under the META-INF directory, but the contents of the file are modified as follows:

customizedSpiService=cn.org.microservice.inter.impl.CustomizedSPIService
microserviceSpiService=cn.org.microservice.inter.impl.MicroserviceSPIService

The following code is the test code of dubbo's SPI. As for the implementation of dubbo ExtensionLoader, we will not explain it here. We will explain dubbo's extension mechanism in detail later. Here is just to introduce the use of ExtensionLoader.

ExtensionLoader<> extensionLoader = ExtensionLoader.getExtensionLoader(SPIService.class);
SPIService spiService = extensionLoader.getExtension("customizedSpiServcice")
spiService.printSpiService()

What dubbo loads is the META-INF/services, META-INF/dubbo, META-INF/dubbo/internal directory to load files named after the full path name of the interface, while Spring Boot uses SpringFactoiesLoader. What it loads is the content of the spring.factories file in the META-INF directory, and the implementation of the Spring Boot SPI mechanism. It is not explained in detail here. You can refer to the blog: Spring Boot Principle Analysis-Automatic Assembly Principle . The loading mechanism of Spring Boot is introduced in detail.

So can we create our own SPI mechanism? The answer is that it is currently possible. I will not introduce specific cases here, but only introduce how to obtain files in all directories under the classpath. There is a getResources() method in the ClassLoader instance to get all the files. Before this, you need to clear the JVM class loading mechanism and the ClassLoader mechanism. The code is as follows:

 SpringApplication.class.getClassLoader().getResources("META-INF/microservice/event.factories");

We will introduce the SPI mechanism of Java here. We will introduce the extension mechanism of Dubbo and the source code of SPI in Dubbo in detail later.

Guess you like

Origin blog.csdn.net/wk19920726/article/details/108580441