• This article analyzes the Java SPI mechanism provided by JDK, which is commonly used in open source projects, hoping to provide reference for everyone in the actual development practice and study of open source projects.

      1 What is SPI

      SPI stands for Service Provider Interface. It is a set of apis provided by Java that can be implemented or extended by third parties. It can be used to enable framework extensions and replace components.

      The overall mechanism is shown as follows:

      The Java SPI is actually a dynamic loading mechanism implemented by a combination of interface-based programming + policy pattern + configuration files.

      Each abstraction of system design, there are often many different implementation schemes. In object-oriented design, it is generally recommended to program based on interface between modules, not hard coding between modules. Once a specific implementation class is involved in the code, it violates the principle of pluggability, and if an implementation needs to be replaced, the code needs to be changed. A service discovery mechanism is needed in order to realize the dynamic specification during module assembly. The Java SPI provides a mechanism for finding a service implementation for an interface. Similar to the IOC idea of moving control of assembly out of the program, this mechanism is especially important in modular design. So the core idea of SPI is decoupling.

      2 Application Scenarios

      In a nutshell, this applies to the implementation policies of the framework that the caller enables, extends, or replaces, depending on actual usage needs

      More common examples:

      • JDBC loads drivers for different types of databases
      • SLF4J loads logging implementation classes from different vendors
      • SPI is used extensively in Spring, such as: For implementation of ServletContainerInitializer servlet3.0 specification, automatic Type Conversion Type Conversion SPI (Converter SPI, the Formatter SPI), etc
      • Dubbo also makes extensive use of SPI extensions to the framework, but it encapsulates the native SPI provided by Java and allows users to extend the Filter interface

      3 Introduction

      To use the Java SPI, follow the following conventions:

      • 1. After the service provider provides an implementation of the interface, create a file named “fully qualified name of the interface” in the META-INF/services directory of the JAR package. The file contains the fully qualified name of the implementation class.
      • 2. The JAR package of the interface implementation class is placed in the classpath of the main program;
      • 3. The main program loads the implementation module dynamically through java.util.ServiceLoder. It scans the meta-INF /services directory to find the fully qualified name of the implementation class and loads the class into the JVM.
      • 4. SPI implementation classes must carry a constructor that takes no arguments.

      The sample code

      Step 1 Define a set of interfaces (suppose org.foo.demo.ishout) and write out one or more implementations of the interfaces (suppose org.foo.demo.animal.Dog, org.foo.demo.animal.Cat).

      public interface IShout { void shout(); } public class Cat implements IShout { @Override public void shout() { System.out.println("miao miao"); } } public class Dog implements IShout { @Override public void shout() { System.out.println("wang wang"); }}Copy the code

      Step 2 Create/meta-INF /services under SRC /main/resources/ and add a file named org.foo.demo.IShout. The contents are the implementation classes to apply (in this case org.foo.demo.animal.Dog and org.foo.demo.animal.Cat, one class per line).

      File location

      - src
          -main
              -resources
                  - META-INF
                      - services
                          - org.foo.demo.IShout
      Copy the code

      The file content

      org.foo.demo.animal.Dog
      org.foo.demo.animal.Cat
      Copy the code

      Step 3 Use the ServiceLoader to load the implementation specified in the configuration file.

      public class SPIMain { public static void main(String[] args) { ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class); for (IShout s : shouts) { s.shout(); }}}Copy the code

      Code output:

      wang wang
      miao miao
      Copy the code

      4 Principle Analysis

      First look at the ServiceLoader signature class member variables:

      public final class ServiceLoader<S> implements Iterable<S>{ private static final String PREFIX = "META-INF/services/"; Private final Class<S> service; // Private final ClassLoader loader is used to locate, load, and instantiate providers. Private Final AccessControlContext ACC; Private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); Private LazyIterator lookupIterator; . }Copy the code

      Refer to the specific ServiceLoader source code, code volume is not much, with comments a total of 587 lines, sort out the process is as follows:

      • The serviceloader. load method creates a new ServiceLoader and instantiates member variables in the class.
        • Loader (ClassLoader type, ClassLoader)
        • Acc (AccessControlContext type, access controller)
        • Providers (LinkedHashMap

          , used to cache successfully loaded classes)
          ,s>
        • LookupIterator (implements iterator functionality)
      • The ServiceLoader determines whether the providers (LinkedHashMap

        ) has a cached instance. If so, return it. If there is no cache, the class is loaded as follows:
        ,s>
      • The ServiceLoader can load the meta-INF /services/ config file from the jar. The ServiceLoader can load the meta-INF /services/ config file from the jar. The ServiceLoader can load the meta-INF/config file from the jar.
              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);
              }
      Copy the code
      • (2) Load Class objects with reflection class.forname () and instantiate the Class with instance().
      • (3) Cache the instantiated class into providers (LinkedHashMap

        ) and return the instance object.
        ,s>

      5 concludes

      Advantages: The advantage of using the Java SPI mechanism is decoupling, so that the assembly control logic of a third-party service module is separated from the caller’s business code, rather than coupled together. Applications can enable framework extensions or replace framework components depending on the business situation.

      Disadvantages:

      • Although ServiceLoader is lazy-loaded, it is basically only available through traversal, which means that the implementation classes of the interface are loaded and instantiated. If you don’t want to use some implementation class, it gets loaded and instantiated, which is wasteful. The method of obtaining an implementation class is not flexible. The method can only be obtained in the form of Iterator. The corresponding implementation class cannot be obtained according to a parameter.
      • It is not safe for multiple concurrent threads to use instances of the ServiceLoader class.

Source: my.oschina.net/1Gk2fdm43/b… Welcome to pay attention to the public number [code farming blossom] learn to grow together I will always share Java dry goods, will also share free learning materials courses and interview treasure book reply: [computer] [design mode] [interview] have surprise oh