Click the above “Yudao Source Code” to select “Top public account”
Technical articles delivered to you first!
High-quality source code column
-
In-depth Dubbo Principles and Source Code 69 Articles
-
In-depth Netty Principles and Source Code 61 Articles
-
Open Source Projects with Detailed Chinese Annotations
-
Java Concurrency Source Code Collection
-
RocketMQ Source Code Collection
-
Sharding-JDBC Source Code Analysis Collection
-
Spring MVC and Security Source Code Collection
-
MyCAT Source Code Analysis Collection
Source: http://t.cn/E2ktNM5
Implementing a Custom SPI1. Project Structure2. Interface Module3. Good Printer Module4. Bad Printer ModuleApplications of SPI in Real ProjectsApplications of SPI in Extensions
The SPI (Service Provider Interface) mechanism provided by JDK may not be familiar to many people, as this mechanism is aimed at vendors or plugins and can also be seen in some framework extensions. The core class java.util.ServiceLoader
is detailed in the JDK 1.8 documentation. Although it is not common, it does not mean it is not often used; on the contrary, you are using it all the time. It’s mysterious, but don’t worry; think about whether you have used third-party logging packages or database drivers in your project? In fact, these are all related to SPI. Furthermore, consider how modern frameworks load logging dependencies and database drivers. You might be familiar with the code class.forName(“com.mysql.jdbc.Driver”)
, which every Java beginner must encounter, but is this how database drivers are still loaded today? Can you still find this code? All these questions will be answered by the end of this article.
First, let’s introduce what the SPI mechanism is.
Implementing a Custom SPI
1. Project Structure

-
Invoker is our main project for testing.
-
Interface is the interface project defined for vendors and plugin providers, providing only the interface without implementation.
-
Good-printer and bad-printer are two different implementations of the interface by different vendors, so they will depend on the interface project.
This simple demo is designed to allow everyone to experience switching the implementation vendor of the interface without changing the invoker code, only by changing the dependencies.
2. Interface Module
2.1 moe.cnkirito.spi.api.Printer
public interface Printer {
void print();
}
The interface only defines a single interface without providing an implementation. The entities that define the specifications are usually quite powerful, and these interfaces are typically located in packages with the prefixes java or javax. Here, Printer simulates a specification interface.
3. Good Printer Module
3.1 good-printer\pom.xml
<dependencies>
<dependency>
<groupId>moe.cnkirito</groupId>
<artifactId>interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
The specific implementation class of the specification must depend on the specification interface.
3.2 moe.cnkirito.spi.api.GoodPrinter
public class GoodPrinter implements Printer {
public void print() {
System.out.println("You are a good person~");
}
}
This is one implementation of the Printer specification interface.
3.3 resources\META-INF\services\moe.cnkirito.spi.api.Printer
moe.cnkirito.spi.api.GoodPrinter
It is important to note that each SPI interface must declare a services file in its project’s static resource directory, with the filename being the full path of the implementing class of the specification interface. In this case, it is moe.cnkirito.spi.api.Printer
, and in the file, the full path of the specific implementation class should be written, which in this case is moe.cnkirito.spi.api.GoodPrinter
.
This completes the implementation of one vendor.
4. Bad Printer Module
We will complete another vendor’s implementation of the Printer specification in the same way as defined in the good-printer module.
4.1 bad-printer\pom.xml
<dependencies>
<dependency>
<groupId>moe.cnkirito</groupId>
<artifactId>interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
4.2 moe.cnkirito.spi.api.BadPrinter
public class BadPrinter implements Printer {
public void print() {
System.out.println("I smoke, drink, party, but I know I am a good girl~");
}
}
4.3 resources\META-INF\services\moe.cnkirito.spi.api.Printer
moe.cnkirito.spi.api.BadPrinter
This completes the implementation of another vendor.
5 Invoker Module
The invoker here is our own project. If we want to use the Printer implementation from the good-printer vendor, we need to introduce its dependency.
<dependencies>
<dependency>
<groupId>moe.cnkirito</groupId>
<artifactId>interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>moe.cnkirito</groupId>
<artifactId>good-printer</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
5.1 Writing the Main Class
public class MainApp {
public static void main(String[] args) {
ServiceLoader<Printer> printerLoader = ServiceLoader.load(Printer.class);
for (Printer printer : printerLoader) {
printer.print();
}
}
}
The ServiceLoader is a loader provided by java.util
for loading files in a fixed class path, and it loads the implementation classes declared for the corresponding interface.
5.2 Print Result 1
You are a good person~
If in subsequent scenarios, we want to replace the vendor’s Printer implementation, we only need to change the dependency.
<dependencies>
<dependency>
<groupId>moe.cnkirito</groupId>
<artifactId>interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>moe.cnkirito</groupId>
<artifactId>bad-printer</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
The main class does not need to change the code, which complies with the Open/Closed Principle.
5.3 Print Result 2
I smoke, drink, party, but I know I am a good girl~
Isn’t it amazing? Everything is transparent to the caller; you just need to switch dependencies!
Applications of SPI in Real Projects
First, let’s summarize what new knowledge we have gained. The files under resources/META-INF/services seem to be something we have not encountered much before, and we also haven’t dealt much with ServiceLoader. Now let’s open our project’s dependencies and see what we find.
-
In mysql-connector-java-xxx.jar, we found the META-INF\services\java.sql.Driver file, which contains only two lines:
com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver
We can analyze that
java.sql.Driver
is a specification interface, andcom.mysql.jdbc.Driver
andcom.mysql.fabric.jdbc.FabricMySQLDriver
are the implementations of this specification by mysql-connector-java-xxx.jar. -
In jcl-over-slf4j-xxxx.jar, we found the META-INF\services\org.apache.commons.logging.LogFactory file, which contains only one line:
org.apache.commons.logging.impl.SLF4JLogFactory
I believe no further explanation is needed for everyone to understand the significance of this.
-
There are many more; if you are interested, you can explore the jar packages in your project path.
Since we are talking about database drivers, let’s elaborate a bit more. Do you remember the classic interview question: What exactly does class.forName(“com.mysql.jdbc.Driver”)
do?
First, think about how you would answer that.
We all know that class.forName
is related to the class loading mechanism and will trigger the execution of the static method in the com.mysql.jdbc.Driver
class, thus loading the database driver for the main class. If further asked why its static block does not trigger automatically, you could answer: Because of the special nature of database driver classes, the JDBC specification explicitly requires that the Driver class must register itself with the DriverManager, which necessitates that it be triggered manually by class.forName
, which can be explained in java.sql.Driver
. Is it perfect? Not yet. In the latest DriverManager source code, we can see the following comment, translated as:
The methods
getConnection
andgetDrivers
of theDriverManager
class have been enhanced to support the Java Standard Edition Service Provider mechanism. JDBC 4.0 Drivers must include aMETA-INF/services/java.sql.Driver
file. This file contains the names of JDBC driver implementations forjava.sql.Driver
. For example, to load themy.sql.Driver
class, theMETA-INF/services/java.sql.Driver
file should contain the following entry:my.sql.Driver
Applications no longer need to explicitly load JDBC drivers using
Class.forName()
. Existing programs that useClass.forName()
to load JDBC drivers will continue to work without modification.
We can see that Class.forName
has been deprecated, so the best answer to this question should involve discussing the SPI mechanism in JAVA with the interviewer, and then talk about the evolution of driver loading history.
java.sql.DriverManager
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
Of course, the main content of this section is still to introduce SPI; the driver part is an extension. If you don’t quite understand, you can go back and review the source code of Driver and DriverManager in jdk1.8, and I believe you will gain a lot.
Applications of SPI in Extensions
SPI is not only a standard for vendors but also provides an idea for framework extensions. Frameworks can reserve SPI interfaces, allowing for extension through adding or deleting dependencies without intruding on the code. The premise is that the framework must reserve core interfaces, similar to the interfaces in the interface module of this example, and the rest of the adaptation work is left to the developers.
For example, in my previous article https://www.cnkirito.moe/2017/11/07/spring-cloud-sleuth/, I introduced the extension of the Filter in motan, which also adopted the SPI mechanism. Once you are familiar with this setup, going back to understand the SPI extensions of some frameworks will not feel too unfamiliar.
Welcome to join my knowledge circle to discuss architecture and exchange source code. To join, long press the QR code below:
Source code analysis has been updated in the knowledge circle as follows:
-
“In-depth Dubbo Source Code Analysis Series” 69 articles.
-
“In-depth Netty Source Code Analysis Series” 61 articles.
-
“In-depth Spring Source Code Analysis Series” 35 articles.
-
“In-depth MyBatis Source Code Analysis Series” 34 articles.
-
“Database Entity Design” 17 articles.
-
Preparing to update “In-depth Spring MVC Source Code Analysis Series”
Currently, the “Dubbo Source Code Analysis” directory has been updated in the knowledge circle as follows: 01. Debugging Environment Setup 02. Project Structure Overview 03. Configuration 04. Core Process Overview
05. Extension Mechanism SPI
06. Thread Pool
07. Service Exposure Export
08. Service Reference Refer
09. Registry Center Registry
10. Dynamic Compilation Compile
11. Dynamic Proxy Proxy
12. Service Invocation Invoke
13. Invocation Features
14. Filter
15. NIO Server
16. P2P Server
17. HTTP Server
18. Serialization
19. Cluster Fault Tolerance
20. Graceful Shutdown
21. Log Adaptation
22. Status Check
23. Monitoring Center
24. Management Center
25. Operation and Maintenance Command QOS
26. Link Tracking Tracing
… A total of 69+ articles
Currently, the “Netty Source Code Analysis” directory has been updated in the knowledge circle as follows: 01. Debugging Environment Setup 02. NIO Basics 03. Introduction to Netty 04. Startup Bootstrap
05. Event Polling EventLoop
06. Channel Pipeline ChannelPipeline
07. Channel Channel
08. Byte Buffer ByteBuf
09. Channel Handler ChannelHandler
10. Codec Codec
11. Utility Class Util
… A total of 61+ articles
Currently, the “Database Entity Design” directory has been updated in the knowledge circle as follows:
01. Product Module 02. Transaction Module 03. Marketing Module 04. Common Module
… A total of 17+ articles
Currently, the “Spring Source Code Analysis” directory has been updated in the knowledge circle as follows:
01. Debugging Environment Setup 02. IoC Resource Location 03. IoC BeanDefinition Loading
04. IoC BeanDefinition Registration
05. IoC Bean Retrieval
06. IoC Bean Lifecycle
… A total of 35+ articles
Currently, the “MyBatis Source Code Analysis” directory has been updated in the knowledge circle as follows:
01. Debugging Environment Setup 02. Project Structure Overview 03. MyBatis Interview Questions Collection
04. MyBatis Learning Materials Collection
05. MyBatis Initialization
06. SQL Initialization
07. SQL Execution
08. Plugin System
09. Spring Integration
… A total of 34+ articles