This article was first published on Hollis official account by Chen Caihua. Please contact [email protected] to reprint the articleCopy the code

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(a);
}
public class Cat implements IShout {
    @Override
    public void shout(a) {
        System.out.println("miao miao"); }}public class Dog implements IShout {
    @Override
    public void shout(a) {
        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 可迭代<S>{
private static final String PREFIX = "META-INF/services/";

    // Represents the loaded class or interface
    private final Class<S> service;

    // Class loaders used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // Access control context used when creating the ServiceLoader
    private final AccessControlContext acc;

    // Cache providers in the order they are instantiated
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // lazy search iterator
    privateLazyIterator 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<String,S>) has a cached instance. If so, return it. If there is no cache, the class is loaded as follows:

  • 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.

More exciting, welcome to pay attention to the author’s public account [Distributed System Architecture]

reference

Java Core Technology lecture 36

The Java ™ Tutorials

Java Doc

Service Provider Interface: Creating Extensible Java Applications

Service provider interface

Java ServiceLoader usage and parsing

Java’s basic SPI mechanism

Java SPI mechanism in depth and source code analysis

Introduction to SPI Mechanism