Tell me about your understanding of SPI in Java

preface

Recently, I was asked about SPI during the interview, but I didn't answer. It was mainly for my own reason. I took myself to the ditch, because I talked about the parent delegation model of class loader. Later, I was asked about the scenarios that destroyed the parent delegation model. Then I said that the modularization of SPI, JNDI and jdk9 destroyed the parent delegation. Then I was asked, tell me about your understanding of SPI in Java. Then I was confused. I just knew that it would destroy the parental delegation and what was going on, but I didn't have a deep understanding. I'll sum up this knowledge this time.

What is SPI

SPI is the full name of service provider interface, which literally means the interface to provide services. To explain in detail, it is a set of interfaces provided by java to be implemented or extended by a third party, which realizes the dynamic expansion of the interface, so that the implementation classes of the third party can be embedded into the system like plug-ins.

Eh... This explanation feels a little tongue twisty. Let's talk about its essence.

SPI example

Let's give an example. We create a project and then create a module called SPI interface.

/**
 * @author jimoer
 **/
public interface SpiInterfaceService {

    /**
     * 打印参数
     * @param parameter 参数
     */
    void printParameter(String parameter);
}

Define a module called SPI service one, POM XML depends on SPI interface. Define an implementation class in SPI service one to implement spiinterfaceservice interface.

package com.jimoer.spi.service.one;
import com.jimoer.spi.app.SpiInterfaceService;

/**
 * @author jimoer
 **/
public class SpiOneService implements SpiInterfaceService {
    /**
     * 打印参数
     *
     * @param parameter 参数
     */
    @Override
    public void printParameter(String parameter) {
        System.out.println("我是SpiOneService:"+parameter);
    }
}

Then create the directory meta inf / services under the resources directory of SPI service one, create a file name in this directory, which is the fully qualified name of spiinterfaceservice interface, and write the file content into the fully qualified name of spioneservice implementation class. The effects are as follows:

package com.jimoer.spi.service.two;
import com.jimoer.spi.app.SpiInterfaceService;
/**
 * @author jimoer
 **/
public class SpiTwoService implements SpiInterfaceService {
    /**
     * 打印参数
     *
     * @param parameter 参数
     */
    @Override
    public void printParameter(String parameter) {
        System.out.println("我是SpiTwoService:"+parameter);
    }
}

The directory structure is as follows:

<dependencies>
    <dependency>
        <groupId>com.jimoer.spi</groupId>
        <artifactId>spi-service-one</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.jimoer.spi</groupId>
        <artifactId>spi-service-two</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

Create test class

/**
 * @author jimoer
 **/
public class SpiService {

    public static void main(String[] args) {

        ServiceLoader<SpiInterfaceService> spiInterfaceServices = ServiceLoader.load(SpiInterfaceService.class);
        Iterator<SpiInterfaceService> iterator = spiInterfaceServices.iterator();
        while (iterator.hasNext()){
            SpiInterfaceService sip = iterator.next();
            sip.printParameter("参数");
        }
    }
}

Execution results:

我是SpiTwoService:参数
我是SpiOneService:参数

Through the running results, we can see that all implementations of spiinterfaceservice interface have been loaded into the current project and the call has been executed.

Implementation of SPI

So let's take a look at how SPI is implemented? From the above example, we can see that the core code of SPI mechanism is the following paragraph:

ServiceLoader<SpiInterfaceService> spiInterfaceServices = ServiceLoader.load(SpiInterfaceService.class);

Let's take a look at serviceloader Source code of load () method:

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

See thread currentThread(). getContextClassLoader(); I understand what's going on. This is the thread context class loader, because the thread context class loader is created to reverse the class loading parent delegation model.

Although we know that it destroys parental delegation, we still need to look at the specific implementation.

Find the specific method to implement hasnext () in serviceloader, then continue to look at the implementation of this method.

// 固定路径
private static final String PREFIX = "Meta-INF/services/";

private boolean hasNextService() {
     if (nextName != null) {
         return true;
     }
     if (configs == null) {
         try {
         	// 固定路径+接口全限定名称
             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);
         }
     }
     while ((pending == null) || !pending.hasNext()) {
         if (!configs.hasMoreElements()) {
             return false;
         }
         pending = parse(service,configs.nextElement());
     }
     // 后面next()方法中判断当前类是否已经出现化的时候要用
     nextName = pending.next();
     return true;
 }

It is mainly to load the fully qualified name file of the interface under the meta inf / services / path, and then find the class path of the implementation class to load the implementation class.

Continue to see how the iterator takes out each implementation object. It depends on the next () method that implements the iterator in serviceloader.

private S nextService() {
     if (!hasNextService())
         throw new NoSuchElementException();
     String cn = nextName;
     nextName = null;
     Class<?> c = null;
     try {
     // 直接加载类,无需初始化(因为上面hasNext()已经初始化了)。
         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);
     }
     throw new Error();          // This cannot happen
 }

You can see here how to create objects. First, load the implementation class of the interface in hasnext () and judge whether there is an implementation class of the interface, and then instantiate the implementation class in the next () method.

In fact, there are many functions using SPI mechanism in Java, such as JDBC, JNDI, spring, and even in RPC framework (Dubbo).

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>