table of Contents
Application of SPI in actual projects
Application of SPI in extension
Abstract: Original source https://www.cnkirito.moe/spi/ "Xu Ma" welcome to reprint, keep the abstract, thank you!
The SPI (Service Provider Interface) mechanism provided by the JDK may not be familiar to many people, because this mechanism is for vendors or plug-ins, and can also be seen in some framework extensions. The core classes java.util.ServiceLoader
can be seen in detail in the jdk1.8 documentation. Although it is not very common, it does not mean that it is not commonly used. On the contrary, you are using it all the time. Mysterious, don't worry, think about whether you use third-party log packages in your project, and whether you use database drivers? In fact, these are all related to SPI. Think about it again. How modern frameworks load log dependencies and load database drivers. You may be familiar with the code class.forName("com.mysql.jdbc.Driver"), which is a must for every Java beginner Encountered, but is the database driver still loaded like this? Can you still find this code? All these questions will be answered after the end of this article.
First introduce what the SPI mechanism is
Implement a custom SPI
1. Project structure
- Invoker is our main project for testing.
- Interface is an interface project defined for manufacturers and plug-in vendors. It only provides interfaces, not implementations.
- Good-printer and bad-printer are different implementations of interface by two vendors, so they will depend on the interface project.
This simple demo is to let everyone experience, without changing the invoker code, only changing the dependency, switch the interface implementation vendor.
2. Interface module
2.1 moe.cnkirito.spi.api.Printer
public interface Printer {
void print();
}
nterface only defines an interface and does not provide implementation. The formulation of the specification is generally the existence of a relatively cow fork, these interfaces are usually located in the package of java, javax prefix. The Printer here is to simulate a standard interface.
3. The 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 fan must rely on the specification interface
3.2 moe.cnkirito.spi.api.GoodPrinter
public class GoodPrinter implements Printer {
public void print() {
System.out.println("你是个好人~");
}
}
For the realization of Printer specification interface one
3.3 resources\META-INF\services\moe.cnkirito.spi.api.Printer
moe.cnkirito.spi.api.GoodPrinter
It needs to be emphasized here. Each SPI interface needs to declare a services file in the static resource directory of its own project. The file name is the full path of the class name that implements the specification interface. In this case moe.cnkirito.spi.api.Printer
, it is written in the file One line of the full path of the concrete implementation class, in this case it is moe.cnkirito.spi.api.GoodPrinter
.
The realization of such a manufacturer is complete.
4. bad-printer module
We are in the same way as defined in the good-printer module to complete another manufacturer's implementation of the Printer specification.
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("我抽烟,喝酒,蹦迪,但我知道我是好女孩~");
}
}
4.3 resources\META-INF\services\moe.cnkirito.spi.api.Printer
moe.cnkirito.spi.api.BadPrinter
In this way, the realization of another manufacturer is complete.
5 invoker module
The invoker here is our own project. If we want to use the printer implementation of the manufacturer's good-printer at the beginning, 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 and calling 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();
}
}
}
ServiceLoader is java.util
a loader provided to load files under a fixed class path, and it is precisely it that loads the implementation class of the corresponding interface declaration.
5.2 Printing results 1
你是个好人~
If you want to replace the manufacturer’s Printer implementation in a subsequent solution, you only need to replace 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>
Calling the main class does not need to change the code, which is in line with the opening and closing principle
5.3 Printing results 2
我抽烟,喝酒,蹦迪,但我知道我是好女孩~
Isn’t it amazing? All this is transparent to the caller, just switch dependencies!
Application of SPI in actual projects
Summarize the new knowledge first. The files under resources/META-INF/services seem to have not been touched before, and ServiceLoader has not been touched. So now we open the dependencies of our own project and see what we find.
-
The META-INF\services\java.sql.Driver file was found in mysql-connector-java-xxx.jar, which contains only two lines of records:
com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver
We can analyze that if it
java.sql.Driver
is a standardized interface,com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
it is the implementation interface of mysql-connector-java-xxx.jar to this specification. -
The META-INF\services\org.apache.commons.logging.LogFactory file was found in jcl-over-slf4j-xxxx.jar, with only one line of record:
org.apache.commons.logging.impl.SLF4JLogFactory
I believe I don’t need to repeat it, everyone can understand what this means
-
There are many more. If you are interested, you can check the jar packages under the project path.
Now that we talked about database drivers, let’s just say a little bit more, remember a classic interview question: what exactly did class.forName("com.mysql.jdbc.Driver") do?
Think first: how would you answer?
We all know that class.forName is related to the class loading mechanism and will trigger the execution of static methods in the com.mysql.jdbc.Driver class, so that the main class loads the database driver. If you ask further, why is its static block not triggered automatically? Answer: Because of the special nature of the database driver class, the JDBC specification clearly requires the Driver class to register itself with the DriverManager, which causes it to be triggered manually by class.forName. This can be explained in java.sql.Driver. Is it perfect? Not yet, come to the latest DriverManager source code, you can see this comment, the translation is as follows:
DriverManager
The methodsgetConnection
and methods of the classgetDrivers
have been improved to support the Java Standard Edition Service Provider mechanism. JDBC 4.0 Drivers must includeMETA-INF/services/java.sql.Driver
files. This file containsjava.sql.Driver
the name of the JDBC driver implementation. For example, to load themy.sql.Driver
class, theMETA-INF/services/java.sql.Driver
file needs to contain the following entries:my.sql.Driver
Applications no longer need to use
Class.forName()
explicitly loaded JDBC drivers.Class.forName()
Existing programs that currently use the loaded JDBC driver will continue to work without modification.
It can be found that Class.forName has been abandoned, so the best answer to this question should be to involve the interviewer with the SPI mechanism in JAVA, and then talk about the evolution history of the loading driver.
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 content of this section mainly introduces SPI. This is an extension of the driver. If you don't understand it, you can read the source code of Driver and DriverManager in jdk1.8. I believe there will be no small gains.
Application of SPI in extension
SPI is not only a standard specified for manufacturers, but also provides an idea for framework extension. The framework can reserve an SPI interface, so that the framework can be expanded by adding and deleting dependencies without intruding the code. The premise is that the framework must reserve a core interface, which is the similar interface in the interface module in this example, and the rest of the adaptation work is left to the developer.
For example , the extension of Filter in motan introduced in my last article https://www.cnkirito.moe/2017/11/07/spring-cloud-sleuth/ uses the SPI mechanism. After getting familiar with this setting Going back to understand the SPI extensions of some frameworks will not be too unfamiliar.
●The strongest Tomcat8 performance optimization in history
● Why can Alibaba resist 10 billion in 90 seconds? --The evolution of server-side high-concurrency distributed architecture
● B2B e-commerce platform--ChinaPay UnionPay electronic payment function
● Learn Zookeeper distributed lock, let interviewers look at you with admiration
● SpringCloud e-commerce spike microservice-Redisson distributed lock solution
Check out more good articles, enter the official account--please me--excellent in the past
A deep and soulful public account 0.0