你了解SPI吗?我之前一直以为我清楚,自己闹了个笑话之后才发现自己就是个笑话。带你了解spi

本文详细解读了JDBC SPI的实现机制,包括ServiceLoader加载器如何动态加载数据库驱动,以及SpringBoot如何利用SPI进行自动配置。通过对比和类比,深入剖析了SPI在简化数据库切换中的作用。

起因:

今天突然想到问同学们B端的程序,你们打包项目上传然后为什么不用重启主程序啊,我很迷茫,我一直在纠结为什么可以动态的感知你这个包上传了,害(因为知道zookeeper的监控,我一直在纠结这个),然后他说了SPI,我都还是再问怎么实现动态监控的呢。。。。。

复习完(不对学习完)SPI后,脸上一脸的尴尬,心中一万个艹尼玛在飞腾。。。

前言:

spi很多人都知道,这是一个Service Provider Interface 服务提供接口,作用是什么呢?

顾名思义,服务去提供接口。

我们去实现这个服务所提供的接口,按照这个接口所约定的规范编写代码,在我们调用的时候就会去遍历相应的目录下去找对应的类并实例化

是不是一头雾水?

我们接下来从数据库JDBC的spi加载机制,以及springboot的加载机制两方面进行分析;类比学习

数据库JDBC的spi加载机制

原生jdbc的实现过程分析

下面是我们原生态的实现数据库驱动等的操作方法,

我们回忆一下原生的JDBC怎么去处理的我们的链接以及sql语句

首先,加载jdbc驱动,接着,连接数据库,再发送SQL语句,最后返回SQL运行结果

public static void main(String[] args) {
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
            
            try {
                //1、加载数据库驱动
                Class.forName("com.mysql.jdbc.Driver");
                //2、通过驱动管理类获取数据库链接
                connection =  DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
                //3、定义sql语句 ?表示占位符
            String sql = "select * from user where username = ?";
                //4、获取预处理statement
                preparedStatement = connection.prepareStatement(sql);
                //5、设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
                preparedStatement.setString(1, "王五");
                //6、向数据库发出sql执行查询,查询出结果集
                resultSet =  preparedStatement.executeQuery();
                //7、遍历查询结果集
                while(resultSet.next()){
                    User user 
                    System.out.println(resultSet.getString("id")+"  "+resultSet.getString("username"));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                //8、释放资源
                if(resultSet!=null){
                    try {
                        resultSet.close();
                    } catch (SQLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if(preparedStatement!=null){
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if(connection!=null){
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            }

        }

我们发现这种方法实现我们数据库源的切换,以及我们的库切换很繁琐。

所以jdbc(java database connectivity)里面就定义了一种接口,在这个接口里面就规范了我们的数据库的连接信息,我们的数据库厂商就直接实现这个接口,然后按照接口名称的路径存放规则去放置在我们规定目录下面

发现上面的代码在与数据库建立连接的过程中的变量就是我们的数据源和数据库的库数据信息如下:
 

Class.forName("com.mysql.jdbc.Driver");

 connection =  DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");

发现对于不同的数据源其实可以用定义一个接口的服务提供,我们的jdbc就是服务的提供者,我们的各大数据库的厂商就是接口的实现者,我们的提供者就规定我们的约定,让我们的实现者去实现;目的就是适配各大数据库厂商的产品,我就是老大,要么你就别跟着我混,自己去实现,要么你就给我老老实实按我的规定办事。也就是接下来讲的

JDBC代码实现SPI的过程

下面就是jdbc的服务提供的过程,也就是我们的SPI代码的主体实现部分

在我们的我们获取连接的时候就用到了java DriverManager 类,这个类的静态块里面就定义了一个初始化的方法,根据我们的原生jdbc代码就可以知道,其实这个就是我们的SPI的实现过程

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

loadInitialDrivers()这个方法就是我们的SPI的主题

private static void loadInitialDrivers() {
        String drivers;
        /**TODO:下面这段代码就是为了使得我们的部分系统权限可以被使用**/
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        //TODO:接下来这个才是我们的重点
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                //TODO:这里获取一个ServiceLoader的加载器
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

               
                try{
                    //TODO:
                    //c = Class.forName(cn, false, loader);
                    //这里面追到下面执行的是这个方法,可以看到并没有实例化
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                }
                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);
            }
        }
    }

 Java.util里面的一个ServiceLoader的类,里面定义了我们需要扫描的路径地址,加载器,权限,延迟迭代器等

    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

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

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

 ServiceLoader的加载迭代器   返回的就是我们的延迟加载的迭代器lookupIterator

   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();
            }

        };
    }
----------------------------我们的延迟加载器------------------------------------
    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();
        }

    }

补充知识:3.11.5 doPrivileged()方法 - mongotea - 博客园

补充https://www.jianshu.com/p/3fe79e24f8a1

插图

 


​总结下:

JDBC的SPI机制就是

在DriverManager里面的静态块去实现loadInitialDrivers的方法

在这个方法里面调用了一个叫做ServiceLoader加载器的load方法,在这个加载器上就定义了我们的规则 类加载器,权限,扫描路径,迭代器的迭代模式等 。ServiceLoader关键功能就是 获取我们的延迟迭代器 ,接下来通过延迟加载器去缓存和实现加载  具体如下:

                 while(driversIterator.hasNext()) {
                     driversIterator.next();
}

在我们的hasNext就实现了对规定路径下的类搜索,以及返回对应的URL集合以便于下面的next方法进行缓存(未初始化)

最后再进行初始化。

能看完,属实不易,我自己第二遍看都没有看完,哈哈哈,这种东西还是需要自己手动点点看的详细啊!!

[SpringBoot的自动配置SPI](http://t.csdn.cn/PQrjY)

以上是我个人见解,若有错误,还望指正。我不6,但是很想变6

番外

个人的疑问:

doPrivileged()方法,还有有些方法追到底层就成了navicate了

还是没有正确的学习方式,追代码追到底层就成了个迷,很多的方法都没有追下去的勇气,有时候就是一个隐藏的无参构造函数就直接荒废打把时间,还有什么new谁调用谁,追着追着到父类去了,哈哈哈。最害怕的还是反射,反射实现的源码我到现在还是没有学会怎么去看,加油吧,虽然大四了,但是学习嘛,不会迟的,加油。明天接上SpringBoot的SPI机制

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我不6,但是很想变6

我不6,但是很想变6

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值