1. Detailed explanation of Java SPI mechanism + overall knowledge of Springboot [Springboot]

1. What is a comment?

The annotations in java are actually very simple. It can be understood as tagging elements (classes, attributes, methods, etc.). At the same time, annotations can carry some attributes. These attributes can have default values
at runtime. We can Obtain the annotation of the element through reflection, and perform corresponding operations according to the annotation of the element and the attribute of the annotation.

2. What are meta annotations?

  • The role of meta-annotations is to annotate other annotations.Java defines 4 standard meta-annotation types, which are used to provide explanations for other annotation types.
  • These types and the classes they support can be found in the java.lang.annotation package. (@Target, @Retention, @Documented, @Inherited)
    • @Target: Used to describe the scope of use of annotations (ie: where can the described annotations be used, such as classes, methods, or constructors)
    • @Retention: Indicates at what level the annotation information needs to be saved, used to describe the life cycle of the annotation (SOURCE <CLASS <  RUNTIME )
    • @Document: Explain that the annotation will be included in the javadoc
    • @Inherited: Explain that the subclass can inherit the annotation in the parent class

 

 

1. What is SPI?

   The full name of SPI is (Service Provider Interface), which is a service discovery mechanism built into the JDK . SPI is a dynamic replacement discovery mechanism. For example, if there is an interface, you only need to add an implementation if you want to dynamically add an implementation to it at runtime. What we often encounter is the java.sql.Driver interface. Different manufacturers can make different implementations for the same interface. Both mysql and postgresql have different implementations for users. Java's SPI mechanism can find services for a certain interface. achieve.

 

​ As shown in the figure above, the interface corresponds to the abstract SPI interface; the implementer implements the SPI interface; the caller relies on the SPI interface.

​ The definition of the SPI interface is in the caller, which is more conceptually dependent on the caller; the organization is located in the package where the caller is located, and the implementation is located in a separate package.

​ When the service provider provides an implementation of an interface, you need to create a file named after the service interface in the META-INF/services/ directory under the classpath. The content of this file is the specific implementation class of this interface . When other programs need this service, you can search for the configuration file in the META-INF/services/ of the jar package (generally based on the jar package). The configuration file contains the specific implementation class name of the interface. You can load and instantiate it based on this class name, and you can use the service. The tool class for finding service implementation in JDK is: java.util.ServiceLoader.

2. The purpose of SPI

​ Database DriverManager, Spring, ConfigurableBeanFactory, etc. all use the SPI mechanism. Here we take the database DriverManager as an example to see the inside story of its implementation.

​ DriverManager is a tool for managing and registering different database drivers in jdbc. For a database, there may be different database driver implementations. When we use a specific driver implementation, we don't want to modify the existing code, but hope that a simple configuration can achieve the effect. When using mysql driver, there is a question, how does DriverManager obtain a certain driver class? After we use Class.forName("com.mysql.jdbc.Driver") to load the mysql driver, we will execute the static code in it to register the driver to the DriverManager for subsequent use.

Driver implementation

package com.mysql.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

In the static code block of the driver class, call the driver registration method new of DriverManager and pass it to the driver manager as a parameter.

Mysql DriverManager implementation

    /**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

You can see that there is a loadInitialDriversmethod in the internal static code block, and the loadInitialDriversusage uses the spi tool class mentioned above ServiceLoader:

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
    println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }

First find the value of the jdbc.drivers attribute, and then find the driver through the SPI mechanism

public final class ServiceLoader<S>
    implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";
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;
        }

You can see that the resource with the class name file name (here equivalent to Driver.class.getName()) under the META-INF/services/ folder is loaded, and then loaded to the virtual machine.

Note that there is a sentence "Load these drivers, so that they can be instantiated." It means to load the drivers scanned by SPI to trigger their initialization. That triggers their static code block

/**
     * Registers the given driver with the {@code DriverManager}.
     * A newly-loaded driver class should call
     * the method {@code registerDriver} to make itself
     * known to the {@code DriverManager}. If the driver is currently
     * registered, no action is taken.
     *
     * @param driver the new JDBC Driver that is to be registered with the
     *               {@code DriverManager}
     * @param da     the {@code DriverAction} implementation to be used when
     *               {@code DriverManager#deregisterDriver} is called
     * @exception SQLException if a database access error occurs
     * @exception NullPointerException if {@code driver} is null
     * @since 1.8
     */
    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }

Register yourself in the drive list of the drive manager

public class DriverManager {


    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

When getting the connection, call the connection method of the driver manager to get it from the list.

  @CallerSensitive
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }

        return (getConnection(url, info, Reflection.getCallerClass()));
    }
 private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
        if(driver != null) {
            Class<?> aClass = null;
            try {
                aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
            } catch (Exception ex) {
                result = false;
            }

             result = ( aClass == driver.getClass() ) ? true : false;
        }

        return result;
    }

Guess you like

Origin blog.csdn.net/zw764987243/article/details/111503789