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
,s>
, used to cache successfully loaded classes)
- 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
,s>
) and return the instance object.
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