Understanding the SPI Mechanism in Java

The SPI (Service Provider Interface) is a built-in service provider discovery mechanism in JDK.It can find service implementations for a specific interface.Demo:

public class TestSpi {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ServiceLoader<SpiInterface> load = ServiceLoader.load(SpiInterface.class);
        Iterator<SpiInterface> iterator = load.iterator();
        if (iterator.hasNext()) {
            SpiInterface next = iterator.next();
            next.spiInterFaceMethod("hello");
        }
    }
}

Interface:

public interface SpiInterface {
    public List<String> spiInterFaceMethod(String keyword);
}

SPI Implementation Class:

public class FileSearch implements SpiInterface {
    @Override
    public List<String> spiInterFaceMethod(String keyword) {
        System.out.println("Testing SPI mechanism");
        return null;
    }
}

Create a META-INF/services directory under the resources directory, and then create a file with the package path.Understanding the SPI Mechanism in JavaExecution Result:Understanding the SPI Mechanism in JavaSPI Analysis:Understanding the SPI Mechanism in JavaLoad Method:

    /**
     * Creates a new service loader for the given service type, using the
     * current thread's {@linkplain java.lang.Thread#getContextClassLoader
     * context class loader}.
     *
     * <p> An invocation of this convenience method of the form
     * <blockquote><pre>
     * ServiceLoader.load(<i>service</i>)</pre></blockquote>
     *
     * is equivalent to
     * <blockquote><pre>
     * ServiceLoader.load(<i>service</i>, 
     *                    Thread.currentThread().getContextClassLoader())</pre></blockquote>
     *
     * @param  <S> the class of the service type
     * @param  service
     *         The interface or abstract class representing the service
     * @return A new service loader
     */
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

Load Method Analysis:Understanding the SPI Mechanism in Java

    /**
     * Creates a new service loader for the given service type and class
     * loader.
     *
     * @param  <S> the class of the service type
     * @param  service
     *         The interface or abstract class representing the service
     * @param  loader
     *         The class loader to be used to load provider-configuration files
     *         and provider classes, or <tt>null</tt> if the system class
     *         loader (or, failing that, the bootstrap class loader) is to be
     *         used
     * @return A new service loader
     */
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader) {
        return new ServiceLoader<>(service, loader);
    }

A ServiceLoader object is constructed.

private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

Understanding the Constructor:Understanding the SPI Mechanism in JavaReload Method:

    /**
     * Clear this loader's provider cache so that all providers will be
     * reloaded.
     *
     * <p> After invoking this method, subsequent invocations of the {@link
     * #iterator() iterator} method will lazily look up and instantiate
     * providers from scratch, just as is done by a newly-created loader.
     * <p> This method is intended for use in situations in which new providers
     * can be installed into a running Java virtual machine.
     */
     public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

The reload method constructs an object calledLazyIterator.HasNext Method:

 public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

HasNextService Method:

           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());
            }
            nextName = pending.next();
            return true;
        }

Understanding the HasNextService Method:Understanding the SPI Mechanism in JavaUnderstanding the SPI Mechanism in JavaUnderstanding the SPI Mechanism in JavaNext Method:

    public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

NextService Method:

  private S nextService() {
            if (!hasNextService())
                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);
            }
            throw new Error();  // This cannot happen
          }

Understanding the NextService Method:Understanding the SPI Mechanism in JavaThis is the process of the SPI mechanism.Class Loader’s Cast Method:Understanding the SPI Mechanism in JavaDefects of the SPI Mechanism:1.It cannot load on demand; it needs to traverse all implementations and instantiate them, and only then can we find the implementation we need. If we do not want to use certain implementation classes, or if certain class instantiation is time-consuming, they are still loaded and instantiated, which causes waste.2.The way to obtain a specific implementation class is not flexible enough; it can only be obtained in an Iterator form and cannot be retrieved based on a specific parameter.3.Using instances of the ServiceLoader class in multiple concurrent threads is not safe.Recently, I have become fascinated with Java bytecode, perhaps due to my interest. I found that it is like pulling a thread that affects the whole body; everything I had glanced at before now seems to connect. Thus, I review the previous material while looking at new things. Suddenly, this SPI mechanism came to mind. I gradually discovered that knowledge is learned repeatedly. Things are used repeatedly. What is it for? For understanding. Now, I feel that understanding is paramount.

Leave a Comment