JDBC [3] – SPI technology and its use in database connection

Time:2021-9-27

[TOC]

1. What is SPI?

SPI, i.eService Provider Interface, a service provision (interface implementation) discovery mechanism, can be found through theMETA-INF/ServiceFile find the file and load the classes defined in it.
It can generally be used to enable framework extension and replacement components, such as in the most common database connection JDBC,java.sql.Driver, different database manufacturers can implement different interfaces, but how does JDK know what others have? This requiresSPI, you can find the implementation of the interface and operate on it.
Explain in two words:decoupling

2. How to use SPI to provide custom services?

Let’s write a simple example:
JDBC [3] - SPI technology and its use in database connection
Overall project structure:

  • SPI-Project:mavenproject

    • DBInterface:mavenProject, parent isSPI-Project, an interface is definedcom.aphysia.sqlserver.DBConnectionService, don’t do it yourself.
    • MysqlConnection: parent yesSPI-Project, the interface is implementedDBConnectionService, that isMysqlConnectionServiceImpl
    • SqlServerConnection: prarent, tooSPI-Project, implementedDBConnectionService, that isSqlServerConnectionServiceImpl
    • WebProject: test the project and simulate the use of database driver in the web project.

Whether it isMySqlConnectionstillSqlServerConnectionBoth modules are implementedDBInterfaceInterface, and inresource/META-INF/servicesYou need to declare the implemented class under the. The file name is the fully qualified name of the implemented interfacecom.aphysia.sql.DBConnectionService, the file contains the fully qualified name of the specific implementation class, such as:com.aphysia.mysql.MysqlConnectionServiceImpl

JDBC [3] - SPI technology and its use in database connection

POM file of SPI project:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.aphysia</groupId>
    <artifactId>SPI-Project</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <modules>
        <module>DbInterface</module>
        <module>MySqlConection</module>
        <module>SqlServerConnection</module>
        <module>WebProject</module>
    </modules>
</project>

2.1 dbinterface definition interface

Dbinterface is a module of spiproject, which mainly defines a specification (Interface) without any implementation.
POM files are as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SPI-Project</artifactId>
        <groupId>com.aphysia</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>DbInterface</artifactId>
</project>

Defined interface (simulates the database driven scenario provided by Java and defines the driver specification):DBConnectionService.java

package com.aphysia.sql;
public interface DBConnectionService {
    void connect();
}

2.2 simulate MySQL implementation driver

The first implementation of the interface is equivalent to simulating a third partyMysqlExpand the interface:
POM file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SPI-Project</artifactId>
        <groupId>com.aphysia</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>MySqlConection</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.aphysia</groupId>
            <artifactId>DbInterface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

Implements the previously defined interface:
MysqlConnectionServiceImpl

package com.aphysia.mysql;

import com.aphysia.sqlserver.DBConnectionService;

public class MysqlConnectionServiceImpl implements DBConnectionService {
    public void connect() {
        System. Out. Println ("MySQL is connecting...);
    }
}

Declaration implementation, inresource/META-INF.services/Define a file namedcom.aphysia.sql.DBConnection, which reads:

com.aphysia.mysql.MysqlConnectionServiceImpl

2.3 simulate sqlserver implementation driver

SqlServerConnectionIt is also a module. The POM file is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SPI-Project</artifactId>
        <groupId>com.aphysia</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>SqlServerConnection</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.aphysia</groupId>
            <artifactId>DbInterface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

The second implementation of the interface is equivalent to a third partySqlServerExpand the interface:SqlServerConnectionServiceImpl

package com.aphysia.sqlserver;

public class SqlServerConnectionServiceImpl implements DBConnectionService {
    public void connect() {
        System. Out. Println ("sqlserver is connecting...);
    }
}

Declaration implementation, inresource/META-INF.services/Define a file namedcom.aphysia.sql.DBConnection, which reads:

com.aphysia.sqlserver.SqlServerConnectionServiceImpl

2.4 simulate different drivers used by users

For the above two different interface implementations, note that they need to be declared under resource. The file name is the fully qualified name of the base class, and the content is the fully qualified name of the specific implementation class

And when we use the project ourselves? The jar package of which driver must be introduced according to which driver is needed.

For example, we import two implementations in webproject:MysqlConnectionandSqlServerConnection:

    <dependencies>
        <dependency>
            <groupId>com.aphysia</groupId>
            <artifactId>DbInterface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.aphysia</groupId>
            <artifactId>MySqlConection</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.aphysia</groupId>
            <artifactId>SqlServerConnection</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

The test code is as follows:

import com.aphysia.sql.DBConnectionService;

import java.util.ServiceLoader;

public class Test {
    public static void main(String[] args) {

        ServiceLoader<DBConnectionService>  serviceLoader= ServiceLoader.load(DBConnectionService.class);
        for (DBConnectionService dbConnectionService : serviceLoader) {
            dbConnectionService.connect();
        }
    }
}

Output:

MySQL connecting
Sqlserver connecting

What if we only introduce the MySQL implementation into the POM file? The answer is obvious. Only the following sentence will be output:

MySQL connecting

That is, for users, they don’t need to do anything by themselves. They just need to import the package, which is simple and easy to use.

Specific complete code:https://github.com/Damaer/Dem…

3. Implementation principle of serviceloader

ServiceLoaderbe locatedjava.utilUnder the package, the main codes are as follows:


public final class ServiceLoader<S>
    implements Iterable<S>
{
    private static final String PREFIX = "META-INF/services/";
    private final Class<S> service;

    private final ClassLoader loader;

    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }


    private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                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);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }

        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }
    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
        ClassLoader cl = ClassLoader.getSystemClassLoader();
        ClassLoader prev = null;
        while (cl != null) {
            prev = cl;
            cl = cl.getParent();
        }
        return ServiceLoader.load(service, prev);
    }

    public String toString() {
        return "java.util.ServiceLoader[" + service.getName() + "]";
    }

}

We callServiceLoader.load()Getting the implementation of the interface is actually called ServiceLoader(Class<S> svc, ClassLoader cl), it’s full of callsreload()reload()What did you do inside?

FirstproviderEmpty and createLazyIteratorObject,LazyIteratorIs an internal class that implementsIteratorInterface is actually a lazy loaded iterator. When will it load?
When called by the iterator, callhasNextService(), to parseresource/META-INF/servicesThe following implementation, and complete the instantiation of the implementation class. Instantiation here uses reflection and fully qualified class names.class.forName()

When parsing, each line represents an implementation class. The discovered interfaces are cached and placed in theprivate LinkedHashMap<String,S> providersAt the same time, it provides external traversal and iteration methods.

4. Application of SPI

When using MySQL driver, wemysql-connector-java-version.jarIn, one file isResource/service/java.sql.DriverFile, which records:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

That’s the statementjava.sql.DriverThe implementation class iscom.mysql.jdbc.Driver, no manual use is requiredClass.forName()Load manually.

alike,slf4jThe same mechanism is used to realize the expansion function.

This idea, through the service contract — > service implementation — > service automatic registration — > service discovery and use, completes the decoupling between the provider and the user, which is really strong

This article only represents your own learning accumulation records or learning notes. If there is infringement, please contact the author to delete it. No one is perfect, so is the article. Your writing is immature. If you are not talented, don’t spray. If there are mistakes, please point them out. Thank you very much~

The road of technology is not for a while. Mountains are high and rivers are long. Even if it is slow, it will gallop without stopping.
Official account: Qinhai grocery store