SPI mechanism in java

Posted by dustinnoe on Wed, 05 Jan 2022 18:02:11 +0100

java SPI function sharing

1. What is SPI

SPI, Service Provider Interface, is a set of API s provided by java to be implemented or extended by third parties. Its essence is to realize dynamic loading through interface based programming + policy mode + configuration file. It is mainly used by framework developers, such as JDBC driver Java sql. Driver interface. Different database manufacturers complete the operation of the database by implementing the secondary interface. mysql and other databases have different implementation classes for users, while Java SPI mechanism can find specific implementation classes for an interface.

2. Several conventions for realizing SPI

1. After the service provider provides a specific implementation of the interface, create a file named "fully qualified name of the interface" in the META-INF/services directory of the jar package, and the content is the fully qualified name of the implementation class;

2. The jar package where the interface implementation class is located is placed in the classpath of the main program;

3. The main program passes Java util. Serviceloder dynamically loads the implementation module, which finds the fully qualified name of the implementation class by scanning the configuration file in the META-INF/services directory, and loads the class into the JVM;

4. The implementation class of SPI must carry a construction method without parameters;

3. Examples of SPI implementation

Step 1: define the interface
public interface LoadBalance {

    String selectServiceAddress(List<String> serviceAddresses);
}
Step 2: define the implementation class
public class RandomLoadBalance implements LoadBalance {

    @Override
    public String selectServiceAddress(List<String> serviceAddresses) {
        Random random = new Random();
        return serviceAddresses.get(random.nextInt(serviceAddresses.size()));
    }

}
Step 3: add configuration file

Add the META-INF/services / directory under the resources file directory to create a file named after the service interface. The content of this file is the specific implementation class of the interface. The contents are as follows:

com.spi.javaspi.loadbalance.RandomLoadBalance
Step 4: use ServiceLoader to load the implementation class
public static void main(String[] args) {

    ServiceLoader<LoadBalance> loadBalances = ServiceLoader.load(LoadBalance.class);
    Iterator<LoadBalance> matcherIter = loadBalances.iterator();
    while (matcherIter.hasNext()) {
        LoadBalance loadBalance = matcherIter.next();
        System.out.println(loadBalance.getClass().getName());
        System.out.println(loadBalance.selectServiceAddress(Arrays.asList("172.30.30.231", "172.30.30.232", "172.30.30.233")));
    }

}

4. Analysis of SPI usage in JDBC

JDBC (java Data Base Connectivity) is a Java API for executing SQL statements. It can provide unified access to a variety of relational databases. It is composed of a group of classes and interfaces written in java language.

JDBC operation database demo:
Connection con;

    public Connection getConnection() {
        try {
            con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shop?characterEncoding=UTF-8", "root", "123456789");
            System.out.println("Database connection succeeded");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return con;
    }

    public static void main(String[] args) throws Exception {
        JDBCTest c = new JDBCTest();
        Connection connection = c.getConnection();
        PreparedStatement statement = connection.prepareStatement("select * from Product");
        ResultSet resultSet = statement.executeQuery();
        while (resultSet.next()) {
            String productName = resultSet.getString("product_name");
            System.out.println("productName: " + productName);
        }
    }
Correlation class analysis -- DriverManager

Static code block:

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

loadInitialDrivers method:

private static void loadInitialDrivers() {
    String drivers;
    drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
        public String run() {
            return System.getProperty("jdbc.drivers");
        }
    });

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            while (driversIterator.hasNext()) {
                driversIterator.next();
            }

            return null;
        }
    });

    String[] driversList = drivers.split(":");
    for (String aDriver : driversList) {
        Class.forName(aDriver, true,
                ClassLoader.getSystemClassLoader());

    }
}

Register driver method:

private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

public static synchronized void registerDriver(java.sql.Driver driver,
        DriverAction da)
    throws SQLException {
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        throw new NullPointerException();
    }
}
Driver implementation class in mysql
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

Get connection method:

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {

    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
    SQLException reason = null;
    for(DriverInfo aDriver : registeredDrivers) {
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        }
    }
}

5. Analysis of advantages and disadvantages

Advantages: decoupling is realized through SPI, and expansion can be realized without changing the source code

Disadvantages: the SPI of the JDK standard instantiates all implementations of the extension point at one time. If there is an extension implementation, the initialization takes a long time (for example, the initialization of static code blocks takes a long time). If it is not used, it will waste resources

6. Other applications of SPI mechanism

Dubbo, spring, log4j and other frameworks also make extensive use of SPI mechanism