[Java essential] JAVA about SPI mechanism

table of Contents

Implement a custom SPI

1. Project structure

2. Interface module

3. The good-printer module

4. bad-printer module

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

 

  1. Invoker is our main project for testing.
  2. Interface is an interface project defined for manufacturers and plug-in vendors. It only provides interfaces, not implementations.
  3. 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.utila 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.

  1. 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.Driveris a standardized interface, com.mysql.jdbc.Driver
    com.mysql.fabric.jdbc.FabricMySQLDriverit is the implementation interface of mysql-connector-java-xxx.jar to this specification.

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

  3. 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 methods getConnection and  methods of the class  getDrivers have been improved to support the Java Standard Edition  Service Provider  mechanism. JDBC 4.0 Drivers must include  META-INF/services/java.sql.Driver files. This file contains  java.sql.Driver the name of the JDBC driver implementation. For example, to load the  my.sql.Driver class, the META-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

Guess you like

Origin blog.csdn.net/a1036645146/article/details/110954652