Pluggable Component Design Mechanism—SPI

Author: Jingdong Logistics Kong Xiangdong

1. What is SPI?

The full name of SPI is Service Provider Interface, which provides a service interface; it is a service discovery mechanism. The essence of SPI is to configure the fully qualified name of the interface implementation class in the file, and the service loader reads the configuration file and loads the implementation class. . In this way, the implementation class can be dynamically replaced for the interface at runtime. Because of this feature, we can easily provide extended functions for our programs through the SPI mechanism.

As shown below:

Each abstraction of system design often has many different implementation schemes. In object-oriented design, it is generally recommended to program between modules based on interfaces, and the implementation of modules is not hard-coded. Once the code involves specific implementation classes, it violates the pluggable The principle of pulling out. Java SPI provides such a mechanism to find the implementation of a service for a certain interface, which is somewhat similar to the idea of ​​IOC, which moves the control of assembly out of the program, which is especially important in modularization. Rather than saying that SPI is a service discovery mechanism provided by java, it is better to say that it is a decoupling idea.

2. Usage scenario?

  • The database driver loading interface realizes the loading of classes; such as: JDBC loads Mysql, Oracle...
  • The log facade interface implements class loading, such as: SLF4J supports log4j and logback
  • SPI is widely used in Spring, especially the implementation of automatic configuration in spring-boot
  • Dubbo also uses a lot of SPI to implement the extension of the framework. It encapsulates the original SPI and allows users to extend and implement the Filter interface.

3. Introduction to use

To use the Java SPI, the following conventions need to be followed:

  • When the service provider provides a specific implementation of the interface, it is necessary to create a file named "interface fully qualified name" in the META-INF/services directory of the JAR package, and the content is the fully qualified name of the implementation class;
  • The JAR where the interface implementation class is located is placed under the classpath of the main program, that is, the dependency is introduced.
  • The main program dynamically loads the implementation module through java.util.ServiceLoder. It will find the fully qualified name of the implementation class by scanning the files in the META-INF/services directory, load the class into the JVM, and instantiate it;
  • The implementation class of the SPI must carry a constructor with no parameters.

Example:

spi-interface module definition

定义一组接口:public interface MyDriver 

spi-jd-driver

spi-ali-driver

实现为:public class JdDriver implements MyDriver
  public class AliDriver implements MyDriver 

Create a /META-INF/services directory under src/main/resources/, and add a file named after the interface (org.MyDriver file)

The content is the implementation class to be applied respectively com.jd.JdDriver and com.ali.AliDriver

spi-core

Generally, it is the core package provided by the platform, including the strategy of loading and using implementation classes, etc. Here we simply implement the logic: a. No specific implementation is found and an exception is thrown b. If multiple implementations are found, print them separately

public void invoker(){
    ServiceLoader<MyDriver>  serviceLoader = ServiceLoader.load(MyDriver.class);
    Iterator<MyDriver> drivers = serviceLoader.iterator();
    boolean isNotFound = true;
    while (drivers.hasNext()){
        isNotFound = false;
        drivers.next().load();
    }
    if(isNotFound){
        throw new RuntimeException("一个驱动实现类都不存在");
    }
}

spi-test

public class App 
{
    public static void main( String[] args )
    {
        DriverFactory factory = new DriverFactory();
        factory.invoker();
    }
}

1. Introduce the spi-core package and execute the result

2. Introduce spi-core, spi-jd-driver packages

3. Incoming spi-core, spi-jd-driver, spi-ali-driver

4. Principle Analysis

See how we got the specific implementation class just now?

Just two lines of code:

ServiceLoader<MyDriver>  serviceLoader = ServiceLoader.load(MyDriver.class);
Iterator<MyDriver> drivers = serviceLoader.iterator();

So, first we look at the ServiceLoader class:

public final class ServiceLoader<S> implements Iterable<S>{
//配置文件的路径
 private static final String PREFIX = "META-INF/services/";
    // 代表被加载的类或者接口
    private final Class<S> service;
    // 用于定位,加载和实例化providers的类加载器
    private final ClassLoader loader;
    // 创建ServiceLoader时采用的访问控制上下文
    private final AccessControlContext acc;
    // 缓存providers,按实例化的顺序排列
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // 懒查找迭代器,真正加载服务的类
    private LazyIterator lookupIterator;
  
 //服务提供者查找的迭代器
    private class LazyIterator
        implements Iterator<S>
    {
 .....
private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
//全限定名:com.xxxx.xxx
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }


        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
//通过反射获取
                c = Class.forName(cn, false, loader);
            }
            if (!service.isAssignableFrom(c)) {
                fail(service, "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            }
        }
........

The approximate process is as follows:

  • The application calls the ServiceLoader.load method

  • When the application obtains an object instance through an iterator, it will first determine whether there is a cached example object in the providers object, and return it directly if it exists

  • If it does not exist, perform class reprinting and read the configuration file under META-INF/services to obtain the names of all classes that can be instantiated, and the configuration file can be obtained across JARs. Load the object through the reflection method Class.forName() and use Instance() The instantiated class method caches the instantiated class in the providers object and returns synchronously.

5. Summary

Pros: Decoupling

The use of SPI separates the assembly control logic of the third-party service module from the business code of the caller, and will not be coupled together. The application can enable framework extensions and replace framework components according to actual business conditions.

The use of SPI makes it unnecessary to obtain the implementation class in the following ways

  • Code hardcoded import import

  • Specify the fully qualified name of the class for reflection acquisition, such as before JDBC4.0; Class.forName("com.mysql.jdbc.Driver")

shortcoming:

Although ServiceLoader can be regarded as lazy loading, it can only be obtained through traversal, that is, all implementation classes of the interface are loaded and instantiated once. If you don't want to use some implementation class, it is also loaded and instantiated, which causes waste. The way to obtain a certain implementation class is not flexible enough, it can only be obtained in the form of Iterator, and the corresponding implementation class cannot be obtained according to a certain parameter.

6. Contrast

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/8564660