What is the SPI

The Service Provider Interface(SPI) is literally a Service Provider Interface. It has two concepts

  • Service Interface: indicates the Service Interface. If you have experience in object-oriented programming, you will understand what an interface is, and what a service is, but it also means something that provides some functionality. The Service Interface is responsible for setting standards for services
  • Service Provider: indicates the Service Provider. Implement specific services according to standards

These two concepts are similar to interface providers and interface implementors in interface oriented programming.

Service Provider Interface(SPI) separates Service specification from Service implementation through Service Interface and Service Provider. Of course, if that’s all it is, it’s no different from the idea of interface oriented programming.

Importantly, the Service Provider Interface(SPI) also introduces the concept of Service discovery. (Service discovery here is not the same thing as service discovery in ZooKeeper)

The SPIService discovery

In Java, for example, I usually write this when USING the Map type

Create a concrete Map instance
Map<String, String> map = new HashMap<>();
/ / using the Map
map.put("".""); .Copy the code

What’s wrong with writing it this way? What could possibly be wrong with that, some readers might say, as JDK source code is used in this way.

But from SPI’s point of view, this code will write the implementation class of the Map interface dead. What if the system traffic is very heavy after the code goes online, and the HashMap has multiple threads and concurrent problems, and needs to be changed to ConcurrentHashMap

Some people might say, let’s change the code

Create a concrete Map instance
java.util.Map<String, String> map = new ConcurrentHashMap<>();
/ / using the Map
map.put("".""); .Copy the code

That’s fine, but is there a better way to dynamically specify Map implementation classes without changing the code?

SPI said sure, we can set a rule that the code will follow to find the corresponding implementation of the interface. For example, we write the implementation class of the interface in the configuration file and then read the configuration in the code

MapImpl. The properties file

mapImpl=java.util.concurrent.ConcurrentHashMap
Copy the code

The pseudocode is as follows

// Find the configuration file
ResourceBundle resourceBundle = ResourceBundle.getBundle("mapImpl.properties");
// Find the fully qualified name of the implementation class
String name = resourceBundle.getString("mapImpl");
// Load the implementation class and instantiate it
Map<String, String> map = (Map<String, String>) Class.forName(name).newInstance();
/ / using the Map
map.put("".""); .Copy the code

The Map implementation classes are then configured in configuration files rather than written in code

This implementation of finding services by some rule is called service discovery by SPI, somewhat similar to the IOC’s idea of moving control of assembly out of the program

The Map example above is not appropriate, but it does not prevent us from understanding the implications of SPI’s service discovery

Summary of SPI thought

I’m just going to make two things

  1. Service (interface) specifications
  2. A rule for finding the implementation of the service (interface)

The point is the second one, we usually rely on the implementation of the interface when we use the interface. With SPI, we just need a rule that can find the implementation of the interface. We don’t need to rely on the implementation of the interface

This is the core of the SPI idea, which further decouples the interface and its implementation by specifying the discovery mechanism for the interface and its implementation

The application of SPI idea

SPI ideas are widely used. Take the Java ecosystem as an example. JDK, Dubbo, and Spring all provide implementations of SPI

java SPI

The SPI implementation has been built in since Java 1.6. This class is java.util.Serviceloader, and its service discovery mechanism is as follows

  • Files inClassPathMETA-INF/services/Under the path
  • File name Fully qualified name of the interface
  • The file content is the fully qualified name of the interface implementation class

Such as interface for Java. Util. Map, you will need to create under the ClassPath meta-inf/services/Java. Util. The Map file, content, for example

java.util.HashMap
java.util.concurrent.ConcurrentHashMap
Copy the code

Java actually realized the SPI, as early as in 1.4 class for javax.mail. Imageio. SPI. The ServiceRegistry, Java. Util. Much of the content of ServiceLoader are relics from the class, Include the meta-inf/services/path, and use the interface fully qualified name as the filename, etc., and javax.mail. Imageio. Spi. The ServiceRegistry function also more powerful and provides the implementation class.

Use of the Java SPI

Java.util.ServiceLoader is more widely used, and its usage is described here

Again, take the java.util.Map interface as an example

/ / load the meta-inf/services/Java. Util. The Map file
ServiceLoader<java.util.Map> serviceLoader = ServiceLoader.load(java.util.Map.class);
/ / get the meta-inf/services/Java. Util. All implementation class in the Map file
Iterator<java.util.Map> iterator = serviceLoader.iterator();
// Load each implementation class
 while(iterator.hasNext()) {
     iterator.next();
 }
Copy the code

Note that iterator gets all the implementation classes, but it is lazily loaded, so you must call iterator.next () to load the implementation classes into the JVM

/ / meta-inf/services/Java. Util. The Map file can define multiple implementation classes, specific with which one can be custom, such as I choose the last implementation class
List<java.util.Map> list = new ArrayList<>();
while (iterator.hasNext()) {
// Load and initialize the implementation class
java.util.Map map = iterator.next();
list.add(map);
}
// Call configure on the last Configuration class
java.util.Map map = list.get(list.size() - 1);
map.put(""."");
Copy the code

Shortcomings of the Java SPI

Java.util.ServiceLoader

  1. Instead of loading on demand, we need to go through all the implementations, instantiate them, and then find the implementation we need in a loop. If you don’t want to use some implementation class, or if some class is time consuming to instantiate, it is also loaded and instantiated, which is wasteful.

  2. 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.

  3. It is not safe for multiple concurrent threads to use instances of the ServiceLoader class.

JDBC’s use of the Java SPI

The java.sql.Driver interface in JDBC is a good example of using the Java SPI.

JDBC is only responsible for the specification of the database driver. The implementation of a specific database driver is left to various database vendors to implement, and then the specific database driver is loaded through the Java SPI service discovery.

JDBC can only do that, because you just don’t know what database is going to come out in the future

JDBC load database driver core source code

JDBC uses the DriverManager class to load a concrete instance of the java.sql.Driver interface

package java.sql;

public class DriverManager {

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized"); }}Copy the code

LoadInitialDrivers () is executed in the static code block above; This method, the details of this method are as follows

package java.sql;

public class DriverManager {

	private static void loadInitialDrivers(a) {
        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
		Iterator<Driver> driversIterator = loadedDrivers.iterator();
       
        while(driversIterator.hasNext()) { driversIterator.next(); }}}Copy the code

I don’t put all the relevant code is removed, it is to get the meta-inf/services/Java, SQL. The Driver all the implementation class, and then call Iterator. The next () method loads the implementation class

As you may be wondering, the above code only loads the implementation class of the Driver, but does not select a specific Driver. Yes, because the choice should be left to the user, as I did with mysql and PostgreSQL. Do you want DriverManager to die, like using the first Driver forever?

Since the Driver is imported by the user, the user must know which one they want to use. For example, in Spring Boot, I imported MySQL and PostgreSQL drivers via Maven, but I specified to use MySQL drivers

spring:
  datasource:
    url: JDBC: mysql: / / 127.0.0.1 / test
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
Copy the code

Mysql Driver in the meta-inf/services/Java SQL. The Driver file

Does the Java SPI break the parental delegation model?

Some articles on the web, using the JDBC load driver above as an example, argue that the Java SPI breaks the parental delegation model

public static void main(String[] args) {
    Enumeration<Driver> drivers = DriverManager.getDrivers();
    Driver driver;
    while (drivers.hasMoreElements()) {
        driver = drivers.nextElement();
        System.out.println(driver.getClass() + "------" + driver.getClass().getClassLoader());
    }
    System.out.println(DriverManager.class.getClassLoader());
}
Copy the code

Load the drivers using JDBC and look at the classloaders that compare them

class com.mysql.cj.jdbc.Driver------sun.misc.Launcher$AppClassLoader@18b4aac2
null
Copy the code

You can see from the results

Com.mysql.cj.jdbc.driver is loaded by Application ClassLoader

The DriverManager is null, which is loaded by Bootstrap ClassLoader

Because of the parent delegate model, the parent loader cannot get classes loaded through the child loader. DriverManager: com.mysql.cj.jdbc.driver: Application ClassLoader: com.mysql.cj.jdbc.driver So they say that the Java SPI breaks the parental delegation model

That seems reasonable, but if you think about it, what does the Java SPI know about an implementation of the SPI idea have to do with the parent-delegate model?

How to explain the above problem? This is easy, because the Java SPI is loaded by the serviceloader.load () method. You just need to click in the serviceloader.load () method to see why

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}
Copy the code

Each thread has its own ContextClassLoader, SystemClassLoader by default. But we can through the Thread. SetContextClassLoader (this cl) method, the an instance of this in one Thread, make the this a relatively Shared instance, This allows even code loaded using the Bootstrap class loader to access classes in the application class loader in this way.

That is not the Java SPI destroyed the parents delegation model, but the Thread. SetContextClassLoader (this cl) this method destroys the delegation model to that of their parents

Spring SPI

Spring also implements its own SPI that functions and works like the Java SPI, with the fully qualified names of the implementation classes in a fixed file called meta-INF/Spring.Factories

For example,

It’s easy to use, for example

// Get the MongoRepositoryFactory configured in all the Factories files
List<MongoRepositoryFactory> factories = SpringFactoriesLoader.loadFactories(MongoRepositoryFactory.class, Thread.currentThread().getContextClassLoader());
Copy the code

The ClassPath contains multiple Spring. factories, and spring SPI supports loading these spring.factories in the order of the ClassPath to an ArrayList

Spring SPI puts the implementations of all interfaces in one fixed file, whereas Java SPI puts the implementations of different interfaces in corresponding files. Whether it is better to use a single file or a separate file for each interface is a matter of opinion

In Spring Boot, the ClassLoader loads files in the project in preference to files in dependent packages. So if you define a Spring. factories file in your project, the files in your project will be loaded first, and so will the implementation class configured in the spring.factories file

reference

javase 6 api

1.4 javase apis

[# JDK/Dubbo/Spring, which one is better?] (# JDK/Dubbo/Spring, which is better?)

JDBC class loader, with SPI service mechanism

# SPI ClassLoader problem

ClassLoader and parental delegation mode and “SPI”

ClassLoader and SPI mechanisms in Java

Java SPI,

Java class loaders and SPI

Java SPI thought comb

Java – SPI mechanisms

Design principles: A brief discussion of SPI and API

# Understand the Java SPI mechanism in depth