Location>code7788 >text

Review of SPI mechanisms (Java SPI, Spring SPI, Dubbo SPI)

Popularity:444 ℃/2024-10-28 22:40:44

The full name of SPI isService Provider Interface, a service discovery mechanism.

The essence of SPI is to combineinterface implementation class(used form a nominal expression)The fully qualified name is configured in the filein, and byThe service loader reads the configuration file and loads the implementation classes. This makes it possible to dynamically replace implementation classes for interfaces at runtime. Because of this feature, it is easy to provide extensions to our programs through the SPI mechanism.

1 Java SPI Example

This section demonstrates the use of Java SPI with an example. First, we define an interface called Robot.

public interface Robot {
    void sayHello();
}

Next define the two implementation classes, which areOptimusPrime respond in singingBumblebee

public class OptimusPrime implements Robot {
    
    @Override
    public void sayHello() {
        ("Hello, I am Optimus Prime.");
    }
  
}

public class Bumblebee implements Robot {

    @Override
    public void sayHello() {
        ("Hello, I am Bumblebee.");
    }
  
}

acceptMETA-INF/services Create a file in the folder with the fully qualified name of Robot . The contents of the file are the fully qualified class name of the implementing class, as follows:



Make the required preparations and next write the code for testing.

public class JavaSPITest {
    @Test
    public void sayHello() throws Exception {
        ServiceLoader<Robot> serviceLoader = ();
        ("Java SPI");
        // 1. forEach paradigm
        (Robot::sayHello);
        // 2. 迭代器paradigm
        Iterator<Robot> iterator = ();
        while (()) {
            Robot robot = ();
       	  //(robot);
          //();
        }
    }
}

Finally, take a look at the test results as follows :

2 Classic Java SPI Applications : JDBC DriverManager

existJDBC4.0 Previously, when we developed a connection to a database, we usually loaded the database-related driver first, and then performed operations such as obtaining a connection.

// STEP 1: Register JDBC driver
("");
// STEP 2: Open a connection
String url = "jdbc:xxxx://xxxx:xxxx/xxxx";
Connection conn = (url,username,password);

JDBC4.0After that, Java's SPI extension mechanism is used, and there is no longer a need to use the("") to load the driver and get the JDBC connection directly.

Next, let's see how the application loads the MySQL JDBC 8.0.22 driver:

in the first placeDriverManagerclass is the driver manager and the entry point for driver loading.

/**
 * Load the initial JDBC drivers by checking the System property
 *  and then use the {@code ServiceLoader} mechanism
 */
static {
     loadInitialDrivers();
     println("JDBC DriverManager initialized");
}

In Java, thestatic block is used for static initialization, which is executed when the class is loaded into the Java Virtual Machine.

The static block will load the instantiated driver, next we look at theloadInitialDrivers Methods.

Loading the driver code consists of four steps:

  1. system variables to get definitions about the driver.

  2. Use SPI to get the driver's implementation class (in the form of a string).

  3. Iterate through the concrete implementations obtained using SPI, instantiating each implementation class.

  4. Instantiate the concrete implementation class based on the list of drivers obtained in the first step.

Let's focus on the usage of SPI, first look at the second step, use SPI to get the driver implementation class , the corresponding code is:

ServiceLoader<Driver> loadedDrivers = ();

There's no going here.META-INF/servicesdirectory to look up the configuration file and didn't load the concrete implementation class, what it did was wrap our interface type and class loader and initialize an iterator.

Moving on to the third step, iterating through the concrete implementations obtained using SPI and instantiating each implementation class, the corresponding code is as follows:

Iterator<Driver> driversIterator = ();
// Iterate through all driver implementations
while(()) {
    ();
}

When traversing, first call the()method, which searches the classpath and the jar packages for all theMETA-INF/servicesdirectory of thefile and find the name of the implementation class in the file; no specific implementation class is instantiated at this point.

Then there's the call to()method, at which point each implementation class is instantiated according to the driver name, and the driver is now found and instantiated.

3 Java SPI Mechanism Source Code Analysis

We follow the first section of the JDK SPI example to learn about theServiceLoader class implementation.

ServiceLoader类

go intoServiceLoader classloadMethods:

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

public static <S> ServiceLoader<S> load(Class<S> service , ClassLoader loader) {
     return new ServiceLoader<>(service, loader);
}

The code above.load method will be passed theType of servicerespond in singingclass loader classLoader Create aServiceLoader Object.

private ServiceLoader(Class<S> svc, ClassLoader cl) {
     service = (svc, "Service interface cannot be null");
     loader = (cl == null) ? () : cl;
     acc = (() != null) ? () : null;
     reload();
}

// Cache service providers that have been instantiated,Stored in order of instantiation
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

public void reload() {
     ();
     lookupIterator = new LazyIterator(service, loader);
}

The private constructor creates a LazyIterator object, which is a lazy iterator.When an object is initialized, it is only initialized and the loading logic is only executed when the iteration method is actually called

After the serviceLoader is created in the sample code, it then calls theiterator()Methods:

Iterator<Robot> iterator = ();

// Iterative method implementation
public Iterator<S> iterator() {
     return new Iterator<S>() {
          Iterator<<String,S>> knownProviders
                = ().iterator();

          public boolean hasNext() {
                if (())
                    return true;
                return ();
          }

          public S next() {
                if (())
                    return ().getValue();
                return ();
          }

          public void remove() {
                throw new UnsupportedOperationException();
          }
     };
}

The implementation of the iteration method is essentially a call to the lazy iterator lookupIterator'shasNext() cap (a poem)next() Methods.

1、hasNext() method

public boolean hasNext() {
      if (acc == null) {
           return hasNextService();
      } else {
           PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                 public Boolean run() { return hasNextService(); }
           };
           return (action, acc);
      }
}
public S next() {
       if (acc == null) {
            return nextService();
       } else {
            PrivilegedAction<S> action = new PrivilegedAction<S>() {
                 public S run() { return nextService(); }
            };
            return (action, acc);
       }
}

Lazy Iterators ofhasNextServicemethod will first get the configuration object through the loader by the full name of the fileEnumeration<URL> configs and then call the parsingparsemethodological analysisclasspathlowerMETA-INF/services/file named after the service interface in the directory.

private boolean hasNextService() {
        if (nextName != null) {
              return true;
        }
        if (configs == null) {
             try {
                 String fullName = PREFIX + ();
                 if (loader == null)
                     configs = (fullName);
                 else
                     configs = (fullName);
              } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
              }
         }
         while ((pending == null) || !()) {
             if (!()) {
                  return false;
             }
             pending = parse(service, ());
         }
         nextName = ();
         return true;
}

(coll.) fail (a student)hasNextService method returns true, we can call the iterator'snext method, which essentially calls the lazy loader lookupIterator'snext() Methods:

2、next() methodologies

Robot robot = ();

// Calling the lazy loader lookupIterator (used form a nominal expression) `next()` methodologies
private S nextService() {
          if (!hasNextService())
              throw new NoSuchElementException();
          String cn = nextName;
          nextName = null;
          Class<?> c = null;
          try {
              c = (cn, false, loader);
          } catch (ClassNotFoundException x) {
             fail(service,
                   "Provider " + cn + " not found");
          }
          if (!(c)) {
              fail(service,
                     "Provider " + cn + " not a subtype");
          }
          try {
              S p = (());
              (cn, p);
              return p;
          } catch (Throwable x) {
              fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
          }
          throw new Error(); // This cannot happen
  }

Through the reflection method() Load the class object withnewInstancemethod instantiates the class and caches the instantiated class into theprovidersIn the object (LinkedHashMap<String,S>type and then returns the instance object.

4 Flaws in the Java SPI Mechanism

The above parsing reveals a flaw in our use of the JDK SPI mechanism:

  • It can't be loaded on demand, it needs to be traversed through all implementations and instantiated, and then in a loop in order to find the implementation we need. If you don't want to use certain implementation classes, or if certain class instantiation is time-consuming, it is also loaded and instantiated, which is wasteful.
  • The way to get an implementation class is not flexible enough, you can only get it in the form of an Iterator, you can't get the corresponding implementation class based on a parameter.
  • It is not safe for multiple concurrent multithreaded instances of the ServiceLoader class to be used.

5 Spring SPI Mechanism

The Spring SPI follows the design philosophy of the Java SPI in that Spring uses the The SPI mechanism is implemented in a way that provides the extensibility of the Spring framework without modifying the Spring source code.

1. Create the MyTestService interface

public interface MyTestService {
    void printMylife();
}

2. Create the MyTestService interface implementation class.

  • WorkTestService :
public class WorkTestService implements MyTestService {
    public WorkTestService(){
        ("WorkTestService");
    }
    public void printMylife() {
        ("My job.");
    }
}
  • FamilyTestService :
public class FamilyTestService implements MyTestService {
    public FamilyTestService(){
        ("FamilyTestService");
    }
    public void printMylife() {
        ("My Family");
    }
}

3. In the resource file directory, create a fixed fileMETA-INF/

#key is the fully qualified name of the interface, value is the implementation class of the interface
 = ,

4. Run the code

// Call the method to load instances of all the classes that implement the MyTestService interface.
List<MyTestService> myTestServices = (
            myTestServices = (
            ().getContextClassLoader()
);

for (MyTestService testService : myTestServices) {
     ();
}

Run results:

FamilyTestService
WorkTestService
My Family
My Work

The Spring SPI mechanism is very similar, but there are some differences:

  • Java SPI is a service provider interface corresponds to a configuration file, the configuration file stores all the implementation classes of the current interface, multiple service provider interfaces correspond to multiple configuration files, all configurations are in the services directory.
  • Spring SPI is a configuration file that stores multiple interfaces and their corresponding implementation classes, configured with the fully qualified name of the interface as the key and the implementation class as the value, with the multiple implementation classes separated by commas, and with only the A configuration file.

As with Java SPI.Spring SPI can't fetch a fixed implementation either, it can only fetch all implementations in order.

6 Dubbo SPI Mechanism

Instead of using Java SPI, Dubbo reimplemented a more powerful SPI mechanism based on the shortcomings of Java SPI, which does not support on-demand loading of interface implementation classes.

The logic related to Dubbo SPI is encapsulated in the ExtensionLoader class. Through ExtensionLoader, we can load the specified implementation classes.

The configuration files required for Dubbo SPI need to be placed in theMETA-INF/dubbo path, the configuration is as follows:

optimusPrime = 
bumblebee = 

Unlike Java SPI implementation class configuration, Dubbo SPI is configured via key-value pairs so that we can load the specified implementation class on demand.

Also, when testing Dubbo SPI, you need to annotate the Robot interface with @SPI.

Here's a demonstration of Dubbo SPI usage:

public class DubboSPITest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader = 
            ();
        Robot optimusPrime = ("optimusPrime");
        ();
        Robot bumblebee = ("bumblebee");
        ();
    }
}

The test results are as follows :

Also.In addition to support for on-demand loading of interface implementation classes, Dubbo SPI adds features such as IOC and AOP.


Dubbo SPI :

/zh-cn/docsv2.7/dev/source/dubbo-spi/

JDK/Dubbo/Spring three SPI mechanism, who is better?

/a/1190000039812642