The SPI mechanism, short for Service Provider Interface, is a Service discovery mechanism. It automatically loads the classes defined in the files by looking for them in the META-INF/services folder in the ClassPath path. This mechanism provides the possibility for many framework extensions, such as the USE of SPI mechanisms in Dubbo and JDBC. This article introduced the Java SPI mechanism and how it is implemented in both modular and non-modular projects (where modularity refers to the modularity introduced in Java9)

Introduction to SPI Mechanism

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

The difference between SPI and API

Application Programming Interfaces (apis) In most cases, the implementer formulates the Interface and implements the Interface. The caller only relies on the Interface invocation and does not have the right to choose different implementations. In terms of users, apis are used directly by application developers. As shown in the figure below, module A is the developer and implementer of the interface, while module B is the user of the interface.

Wikipedia’s API description: In building applications, an API (application programming interface) simplifies programming by abstracting the underlying implementation and only exposing objects or actions the developer needs. While a graphical interface for an email client might provide a user with a button that performs all the steps for fetching and highlighting new emails, an API for file input/output might give the developer a function that copies a file from one location to another without requiring that the developer understand the file system operations occurring behind the scenes.

The Service Provider Interface (SPI) allows the caller to formulate the Interface specification and provide it to the external for implementation. The caller selects the required external implementation during invocation, which can be used to enable framework extensions and replaceable components. In terms of users, SPI is used by framework extenders. As shown in the figure below, module A defines and uses the interface, while module B implements the interface.

SPI Wikipedia definition: Service Provider Interface (SPI) is an API intended to be implemented or extended by a third party. It can be used to enable framework extension and replaceable components

SPI Java A service is a well-known set of interfaces and (usually abstract) classes. A service provider(SPI) is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the application’s class path or by some other platform-specific means.

The advantage of SPI

The advantage of using the Java SPI mechanism is decoupling, so that the definition of an interface is separated from the concrete business implementation, rather than coupled together. Application processes can enable or replace specific components based on actual services. Take JDBC database Driver in Java as an example, Java officially developed the java.SQL.Driver database Driver interface in the core library, using the interface to achieve the database link and other logic, but did not specifically implement the database Driver interface. It’s left to vendors like MySql to implement specific database interfaces.

From the blog: The Java SPI is primarily used in vendor-defined components or plug-ins. This is explained in more detail in the java.util.ServiceLoader documentation. To summarize the idea of Java SPI mechanism, there are many different implementations of abstract modules in our system, such as logging module, XML parsing module, JDBC module and so on. In object-oriented design, we generally recommend interface programming between modules rather than hard-coding implementation classes 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.

The provisions of the SPI

Service providers need to use conventions to tell the system the location of their services. After Java9, there are two conventions:

  1. This is done by configuring related files in the meta-INF /services/ directory.
  2. The service location is specified by the Java9 Jigsaw export statement.

    A service provider is a single type, usually a concrete class. An interface or abstract class is permitted because it may declare a static provider method, discussed later. The type must be public and must not be an inner class. A service provider and its supporting code may be developed in a module, which is then deployed on the application module path or in a modular image. Alternatively, a service provider and its supporting code may be packaged as a JAR file and deployed on the application class path. The advantage of developing a service provider in a module is that the provider can be fully encapsulated to hide all details of its implementation. An application that obtains a service loader for a given service is indifferent to whether providers of the service are deployed in modules or packaged as JAR files. The application instantiates service providers via the service loader’s iterator, or via Provider objects in the service loader’s stream, without knowledge of the service providers’ locations.

Modular statement convention

The modularization statement convention applies to projects that are modularized. Take java.sql.Driver as an example. Add the following statements to the modularization file module-info.java to provide specified services for applications:

provides java.sql.Driver with com.wangzemin.learning.provider.TestDriverProvider;

The following uses a custom java.sql.Driver service provider (or you can optionally define an interface with another module) as an example to show how SPI conventions can be used in the case of Java modularity.

1. Sample project directory structure: This article provides a complete example for testing three Main files, TestDriverProvider(custom Driver service provider), module-info.java (Java modularity file), and Main (debug and output).

2. The content of the example file testDriverProvider.java

public class TestDriverProvider implements Driver {
    // Override's methods are empty.
}
Copy the code

Module file module-info.java:

module provider {
    uses java.sql.Driver;
    requires java.sql;
    // Specify the customized TestDriverProvider as the Driver service provider
    provides java.sql.Driver with com.wangzemin.learning.provider.TestDriverProvider;
}
Copy the code

The main function (ServiceLoader is used to find the appropriate service provider, more on this later) :

public class Main {
    public static void main(String[] args) {
        ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
        for (Driver item : loader) {
            System.out.println("Get class:"+ item.getClass().descriptorString()); }}}Copy the code

3. Example output

Get class:Lcom/wangzemin/learning/provider/TestDriverProvider;

From the output, you can see that the ServiceLoader can be successfully located to the TestDriverProvider.

Modularity Agreement A service provider that is developed in a module must be specified in a provides directive in the module declaration. The provides directive specifies both the service and the service provider; this helps to locate the provider when another module, with a uses directive for the service, obtains a service loader for the service. It is strongly recommended that the module does not export the package containing the service provider. There is no support for a module specifying, in a provides directive, a service provider in another module. A service provider that is developed in a module has no control over when it is instantiated, since that occurs at the behest of the application, but it does have control over how it is instantiated: If the service provider declares a provider method, then the service loader invokes that method to obtain an instance of the service provider. A provider method is a public static method named “provider” with no formal parameters and a return type that is assignable to the service’s interface or class. In this case, the service provider itself need not be assignable to the service’s interface or class. If the service provider does not declare a provider method, then the service provider is instantiated directly, via its provider constructor. A provider constructor is a public constructor with no formal parameters. In this case, the service provider must be assignable to the service’s interface or class A service provider that is deployed as an automatic module on the application module path must have a provider constructor. There is no support for a provider method in this case. As an example, suppose a module specifies the following directives:

 provides com.example.CodecFactory with com.example.impl.StandardCodecs;
 provides com.example.CodecFactory with com.example.impl.ExtendedCodecsFactory;
Copy the code

where com.example.CodecFactory is the two-method service from earlier. com.example.impl.StandardCodecs is a public class that implements CodecFactory and has a public no-args constructor. com.example.impl.ExtendedCodecsFactory is a public class that does not implement CodecFactory, but it declares a public static no-args method named “provider” with a return type of CodecFactory. A service loader will instantiate StandardCodecs via its constructor, and will instantiate ExtendedCodecsFactory by invoking its provider method. The requirement that the provider constructor or provider method is public helps to document the intent that the class (that is, the service provider) will be instantiated by an entity (that is, a service loader) which is outside the class’s package.

Configuration file Conventions

The configuration file convention applies when the project is not modularized, and you need to create a document named after the service interface in the META-INF/services/ directory of the CLASspath. The contents of this document are the specific implementation classes of the interface.

The following also uses a custom java.sql.Driver service provider (which can optionally define an interface with another module) as an example to show how SPI can be used under Java configuration file conventions.

1. Sample project directory structure: This article provides a complete example of three Main files for testing, TestDriverProvider(custom Driver service provider, same as above), Main (debug and output, same as above), Meta-inf/services/Java. SQL. Driver files (is used to specify the location of the service provides the)

2. The sample files TestDriverProvider and the Main content and above all, don’t again in detail, here only show the meta-inf/services/Java SQL. The content of the Driver file, the file contains only one line:

com.wangzemin.learning.learning.provider.TestDriverProvider

3. Example output

Get class:Lcom/wangzemin/learning/provider/TestDriverProvider;

From the output, you can see that the ServiceLoader can be successfully located to the TestDriverProvider.

A service provider that is packaged as A JAR file for the class path is identified by Placing A provider-configuration file in the resource directory META-INF/services. The name of the provider-configuration file is the fully qualified binary name of the service. The provider-configuration file contains a list of fully qualified binary names of service providers, one per line. For example, suppose the service provider com.example.impl.StandardCodecs is packaged in a JAR file for the class path. The JAR file will contain a provider-configuration file named: META-INF/services/com.example.CodecFactory that contains the line: com.example.impl.StandardCodecs # Standard codecs The provider-configuration file must be encoded in UTF-8. Space and tab characters surrounding each service provider’s name, as well as blank lines, are ignored. The comment character is ‘#’ (‘\u0023’ NUMBER SIGN); on each line all characters following the first comment character are ignored. If a service provider class name is listed more than once in a provider-configuration file then the duplicate is ignored. If a service provider class is named in more than one configuration file then the duplicate is ignored. A service provider that is mentioned in a provider-configuration file may be located in the same JAR file as the provider-configuration file or in a different JAR file. The service provider must be visible from the class loader that is initially queried to locate the provider-configuration file; this is not necessarily the class loader which ultimately locates the provider-configuration file.

Principle of SPI

Given the conventions of SPI described above, how does the SPI mechanism locate and load the corresponding service provider’s classes? The loading of the SPI service can be divided into two parts:

  1. Obtaining the class full name qualified name, that is, knowing which classes are service providers.
  2. Class loading, loading the acquired class into memory, involving the context class loader.

Class qualified name retrieval

Modular case

Refer to the Official Jigsaw documentation. The MODULAR syntax of Jigsaw itself supports the SPI service. By providing XXXX with YYYY, you can specify a service provider YYYY for the XXXX service.

Official documentation: Services allow for loose coupling between service consumers modules and service providers modules.This example has a service consumer module and a service provider module:

  1. module com.socket exports an API for network sockets. The API is in package com.socket so this package is exported. The API is pluggable to allow for alternative implementations. The service type is class com.socket.spi.NetworkSocketProvider in the same module and thus package com.socket.spi is also exported.
  2. module org.fastsocket is a service provider module. It provides an implementation of com.socket.spi.NetworkSocketProvider. It does not export any packages.

In the case of configuration

When configured, serviceloader.load iterates through all classes in the meta-INF /services file named for that class, based on the incoming interface class, and then loads the services using the class loader.

The class loader is loaded

Once you have obtained the file of the SPI service implementation class, you can use the class loader to load the corresponding class into memory. The problem is that SPI interfaces are part of the Java core library and are loaded by the boot class loader. Java classes implemented by SPI are typically loaded by the system class loader. The boot class loader cannot find SPI’s implementation class because it only loads Java’s core libraries. It also cannot delegate to the system classloader because it is the ancestor of the system classloader. That is, the parent delegate model of the class loader does not solve this problem. So Java uses a thread-context class loader. By breaking the “parent delegate model,” the parent delegate load chain pattern can be discarded in the execution thread, allowing programs to use class loaders in reverse order to load SPI services. The thread context classloader is implemented as follows:

  1. Class loaders in the current thread are stored in ThreaLocal via setContextClassLoader(ClassLoader CL), which defaults to AppClassLoader.
  2. When a program in the Java core library needs to load the SPI implementation class, it first gets the context ClassLoader through the getContextClassLoader(ClassLoader CL) method in ThreaLocal, and then loads the SPI implementation class through the ClassLoader.

ServiceLoader

Refer to official documentation. The ServiceLoader is a tool for loading the SPI service implementation classes and can handle zero, one, or more service providers.

Official note: A facility to load implementations of a service.A service is a well-known interface or class for which zero, one, or many service providers exist. A service provider (or just provider) is a class that implements or subclasses the well-known interface or class. A ServiceLoader is an object that locates and loads service providers deployed in the run Time environment at a time of an application’s choosing. Application code refers only to the service not to service providers, and is assumed to be capable of differentiating between multiple service providers as well as handling the possibility that no service providers are located.

The main method is public static ServiceLoader load(Class service, ClassLoader), which is loaded according to the SPI interface and the Class loader (default thread context Class loader). Generate a ServiceLoader, which can get the SPI implementation class as an iterator, Iterotor, or stream. The loading is mainly divided into two parts: modular service class loading and modular class loading. Finally, all the loaded implementation classes are sorted. Note: Some exceptions may occur if the loaded service class contains network resources.

If the class path of the class loader includes remote network URLs then those URLs may be dereferenced in the process of searching for provider-configuration files. This activity is normal, although it may cause puzzling entries to be created in web-server logs. If a web server is not configured correctly, however, then this activity may cause the provider-loading algorithm to fail spuriously. A web server should return an HTTP 404 (Not Found) response when a requested resource does not exist. Sometimes, however, web servers are erroneously configured to return an HTTP 200 (OK) response along with a helpful HTML error page in such cases. This will cause a ServiceConfigurationError to be thrown when this class attempts to parse the HTML page as a provider-configuration file. The best solution to this problem is to fix the misconfigured web server to return the correct response code (HTTP 404) along with the HTML error page.

As mentioned above, SPI may have many service providers, but only some of them are useful. In this case, we need to filter the service implementation classes obtained by the ServiceLoader. For example, we only need the PNG CodecFactory. We can then add a custom @png annotation to the corresponding service implementation class and filter it to get the desired service provider:

 ServiceLoader<CodecFactory> loader = ServiceLoader.load(CodecFactory.class);
 Set<CodecFactory> pngFactories = loader
        .stream()                                              // Note a below
        .filter(p -> p.type().isAnnotationPresent(PNG.class))  // Note b
        .map(Provider::get)                                    // Note c
        .collect(Collectors.toSet());
Copy the code

I am the god of the royal Fox. Welcome to follow my wechat official account

This article was first published to wechat public account, all rights reserved, reprint prohibited!