In-depth understanding of the mechanism in Java SPI

This article first appeared in vivo micro-channel public number of Internet technology 
links: https://mp.weixin.qq.com/s/vpy5DJ-hhn0iOyp747oL5A
Author: Jiang column

SPI (Service Provider Interface), is the built-in JDK provides a service discovery mechanism, this article describes progressive approach to Java SPI mechanism.

I. Introduction

The SPI (Service Provider Interface) , is JDK built a service provider discovery mechanism can be used to enable the framework to extend and replace components, mainly by developers using frameworks such as java.sql.Driver interfaces, various other vendors can target the same interface to make different implementations, MySQL and PostgreSQL have different implementations available to the user, while the SPI Java as an interface mechanism may be looking for service implementation. Java, SPI mechanism main idea is to move beyond the control of the assembly process, this mechanism is particularly important in a modular design, the core idea is to decouple .

SPI and API differences:

  • API calls and is used to describe the classes, interfaces, methods, etc. to achieve the goals;

  • SPI is extended and implemented to achieve the target classes, interfaces, methods, etc. described;

In other words, the API offers classes for specific operations, methods, SPI by operating meet specific class, method.

Reference: https://stackoverflow.com/questions/2954372/difference-between-spi-and-api?answertab=votes#tab-top

SPI whole mechanism is as follows:

When the provider's services implement an interface that after, you need to create a service interface to the file named META-INF under the classpath / services / directory, this file is the content of a specific category of this interface. While other programs require this service, you can find this jar package (usually to do dependent jar package) of the META-INF / services / configuration file, the configuration file name interface implementation class, can be loaded according to instantiate the class name, you can use the service. Find services JDK tools to achieve is: java.util.ServiceLoader.

Second, the application scenarios

SPI extension mechanism there are many scenarios, such as the Common-Logging, JDBC, Dubbo like.

SPI process:

  1. Relevant organizations and interface standards defined by the equation

  2. Third party embodied: to achieve a specific method, the configuration META-INF / services / $ {interface_name} files

  3. Developers use

For example, JDBC next scene:

  • First defined in Java interface java.sql.Driver, and no specific implementation of specific implementations are provided by different vendors.

  • In MySQL jar package mysql-connector-java-6.0.6.jar can be found META-INF / services directory, there will be a name for java.sql.Driver files in the directory, the file content is com.mysql. cj.jdbc.Driver, there's content is the implementation of the interface defined for Java.

  • Also in a jar PostgreSQL PostgreSQL-42.0.0.jar can also find the same configuration file, as is org.postgresql.Driver, this is the realization of PostgreSQL java.sql.Driver of Java.

Third, the use demo

1. Define an interface HelloSPI.

package com.vivo.study.spidemo.spi;
public interface HelloSPI {
    void sayHello();
}

2. Complete the interface of multiple implementations.

package com.vivo.study.spidemo.spi.impl;
import com.vivo.study.spidemo.spi.HelloSPI;
public class ImageHello implements HelloSPI {
    public void sayHello() {
        System.out.println("Image Hello");
    }
}

 

package com.vivo.study.spidemo.spi.impl;
import com.vivo.study.spidemo.spi.HelloSPI;
public class TextHello implements HelloSPI {
    public void sayHello() {
        System.out.println("Text Hello");
    }
}

Com.vivo.study.spidemo.spi.HelloSPI to create a file, this file is the content of a specific category of this interface in the META-INF / services / directory.

Details are as follows:

com.vivo.study.spidemo.spi.impl.ImageHello
com.vivo.study.spidemo.spi.impl.TextHello

 

3. Use ServiceLoader to achieve load specified in the configuration file

com.vivo.study.spidemo.test Package 
Import java.util.ServiceLoader; 
Import com.vivo.study.spidemo.spi.HelloSPI; 
public class SPIDemo { 
    public static void main (String [] args) { 
        the ServiceLoader <HelloSPI> ServiceLoader ServiceLoader.load = (HelloSPI.class); 
        // perform different vendors service implementation, according to the service needs of the specific configuration 
        for (HelloSPI helloSPI: ServiceLoader) { 
            helloSPI.sayHello (); 
        } 
    } 
}

Output:

Image Hello
Text Hello

Fourth, source code analysis

// ServiceLoader implements Iterable interface, you can traverse all of the service implementers 
public ServiceLoader Final class <S> the implements Iterable <S> 
{ 
    // find the config files 
    private static final String PREFIX = "META -INF / services /"; 
    // said to be loaded service class or interface 
    Private class Final <S>-service; 
    // this ClassLoader used to locate, load, instantiate service provider 
    Private Final ClassLoader Loader; 
    // access control context 
    private final AccessControlContext acc; 
    // cache has been instantiated service provider, according to example of the sequential storage 
    Private a LinkedHashMap <String, S> = new new providers a LinkedHashMap <> (); 
    // iterator 
    Private LazyIterator lookupIterator; 
}

 

// 服务提供者查找的迭代器
public Iterator<S> iterator() {
    return new Iterator<S>() {
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();
        // hasNext方法
        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }
        // next方法
        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }
    };
}

 

// Find a service provider iterator 
Private class LazyIterator the implements Iterator <S> { 
    // Service Provider Interface 
    Class <S> Service; 
    // class loader 
    ClassLoader Loader; 
    // save the url implementation class 
    Enumeration <URL> configs null =; 
    // save the full name of the class that implements 
    the iterator <String> Pending null =; 
    // iterator to the next to achieve the full class name 
    String nextName = null; 
 
    public Boolean the hasNext () { 
        IF (! nextName = null) { 
            to true return; 
        } 
        IF (configs == null) { 
            the try { 
                String + service.getName the fullName the PREFIX = (); 
                IF (Loader == null) 
                    configs = ClassLoader.getSystemResources (the fullName);
                else
                    configs = loader.getResources(fullName);
            } catch (IOException x) {
                fail(service, "Error locating configuration files", x);
            }
        }
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
            pending = parse(service, configs.nextElement());
        }
        nextName = pending.next();
        return true;
    }
 
    public S next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service,"Provider " + cn + " not found");
        }
        if (!service.isAssignableFrom(c)) {
            fail(service, "Provider " + cn  + " not a subtype");
        }
        try {
            S p = service.cast(c.newInstance());
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service, "Provider " + cn + " could not be instantiated: " + x, x);
        }
        throw new Error();          // This cannot happen
    }
}

First, ServiceLoader implements the Iterable interface, so it has a property iterator, here it is the main method to achieve a hasNext and next iterator. HasNext and corresponding methods here are mainly next call lookupIterator of, lookupIterator is lazy loading iterator.

Secondly, hasNext method LazyIterator the static variable PREFIX is "META-INF / services /" directory, which is why you need to create a service interface to the file named META-INF under the classpath / services / directory.

Finally, by reflection method the Class.forName () to load the class object, method and use newInstance instantiate the class and the instance of the object class caches providers, (LinkedHashMap <String, S> type) and then return an instance of the object.

Fifth, lack of

1. can not be loaded on demand, you need to traverse all implementations, and instantiate, and then realize that we need to find in the loop. If you do not want to achieve certain class, or certain class instantiation time-consuming, it can also be loaded and instantiated, which resulted in waste.

2. Get an implementation class is not flexible, it can only be obtained through the Iterator form, not to get the corresponding implementation classes based on some parameters.

3. Multiple simultaneous multi-threading use an instance of the class ServiceLoader is unsafe.

Sixth, avoid

For the above shortcomings point, when we SPI selection mechanism, consider using SPI mechanism dubbo achieve.

Specific reference:  http://dubbo.apache.org/zh-cn/blog/introduction-to-dubbo-spi.html

More Stay tuned  vivo Internet technology  micro-channel public number

Note: Please reprint the article with the Micro Signal: labs2020  contact.

Guess you like

Origin www.cnblogs.com/vivotech/p/11431353.html