zoukankan      html  css  js  c++  java
  • java spi机制详解

    1.什么是spi?

    SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。

    如上图所示,接口对应的抽象SPI接口;实现方实现SPI接口;调用方依赖SPI接口。

    SPI接口的定义在调用方,在概念上更依赖调用方;组织上位于调用方所在的包中,实现位于独立的包中。

    当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务实现的工具类是:java.util.ServiceLoader。

    2.spi的用途

    数据库DriverManager、Spring、ConfigurableBeanFactory等都用到了SPI机制,这里以数据库DriverManager为例,看一下其实现的内幕。

    DriverManager是jdbc里管理和注册不同数据库driver的工具类。针对一个数据库,可能会存在着不同的数据库驱动实现。我们在使用特定的驱动实现时,不希望修改现有的代码,而希望通过一个简单的配置就可以达到效果。 在使用mysql驱动的时候,会有一个疑问,DriverManager是怎么获得某确定驱动类的?

     

      先来看看我们平时用的Class.forName("com.mysql.jdbc.Driver")方法,该方法在此处返回com.mysql.jdbc.Driver对象,并执行其中的静态方法把driver注册到DriverManager中,以便后续的使用。

    driver实现

    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!");
            }
        }
    }

    驱动的类的静态代码块中,调用DriverManager的注册驱动方法new一个自己当参数传给驱动管理器。

    jdk中DriverManager实现

        /**
         * 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");
        }

    可以看到其内部的静态代码块中有一个loadInitialDrivers方法,loadInitialDrivers用法用到了上文提到的spi工具类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);
                }
            }
        }
    

      先查找jdbc.drivers属性的值,然后通过SPI机制查找驱动,并且在上述源码中

    可以看到加载META-INF/services/ 文件夹下类名为文件名(这里相当于Driver.class.getName())的资源,然后将其加载到虚拟机。

    注释有这么一句“Load these drivers, so that they can be instantiated.” 意思是加载SPI扫描到的驱动来触发他们的初始化。即触发他们的static代码块

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

      最后我们再来看下mysql-connector.jar包下的文件,有图有真相:

     

     文件名为jdk下的java.sql.driver接口的全限定名,文件内容为mysql驱动包下的实现类com.mysql.cj.jdbc.Driver 

  • 相关阅读:
    关于Excel无法打开,因为文件格式或文件扩展名无效的解决方法
    SpringBoot整合rabbitmq
    异步线程池的使用
    java1.8新特性之stream流式算法
    hashMap的底层实现
    redis底层设计(五)——内部运作机制
    redis底层设计(四)——功能的实现
    redis底层设计(三)——redis数据类型
    redis底层设计(二)——内存映射数据结构
    redis底层设计(一)——内部数据结构
  • 原文地址:https://www.cnblogs.com/kiwi-deng/p/13683772.html
Copyright © 2011-2022 走看看