Java拾遗(一)之SPI机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27605885/article/details/82383190

例子:

        IOperation plus = new PlusOperationImpl();
        IOperation division = new DivisionOperationImpl();
        System.out.println(plus.operation(6, 3));//加法
        System.out.println(division.operation(6, 3));//除法

通常我们要定义一个四则运算接口IOperation,然后会写他的实现类PlusOperationImpl,DivisionOperationImpl。

然后在各自的实现类中先实现接口,实现相应的方法。

但是,java设计来一波很sao的操作。

再看另外一个例子:

            Class.forName("com.mysql.jdbc.Driver");
    		String url = "jdbc:mysql://localhost:3306/test";
    		String username = "root";
    		String password = "123";
    		Connection conn = DriverManager.getConnection(url,username,password);

上面是jdbc获取连接的代码,很平常。但是我们不好奇它是怎么实现的吗?

首先是第一行加载类,所以看看com.mysql.jdbc.Drver

package com.mysql.jdbc;
static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

他有一个静态代码块,我们都知道类在加载的时候是会执行静态代码块的,他new了一个Driver对象,术语叫注册驱动,说人话就是把新建的对象放到内存中去了后面来使用。最终它会存到java.sql.DriverManager类中的CopyOnWriteArrayList<DriverInfo> registeredDrivers属性里。后面会迭代这个list。

第一步事情做完了,下面就是DriverManager.getConnection这个方法获取连接了,

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

然后又调用getConnection方法:

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

        for(DriverInfo aDriver : registeredDrivers) {
            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());
            }

        }
......///
    }

去掉一些,只看关键部分 Connection con = aDriver.driver.connect(url, info);这个才是获取连接的

然后你会发现,他丫的aDriver.driver是个接口,然鹅它就这么调用了。真是会玩,接口也能调。

当然接口肯定不能直接拿来用,上面肯定藏着它的实现类,点进DriverInfo你会发现它它好像内部类,因为它所在文件的类名叫java.sql.DriverManager,仔细看才发现他丫的公用了一个文件才不是什么内部类,也就是只要是同一个包里面就可以new了。

如果你在这行打个断点,debug的时候会发现aDriver.driver不是接口了,已经被初始化了。

原理就是这个类,java.util.ServiceLoader。你要问我,我怎么知道是在这个类里面。秘诀就是debug,driver的初始化时在这个类DriverInfo里面,而这个类就一个构造方法。咱就在这个构造方法打个断点,果然执行了,然后你在一步步debug(真是很晕,你会N多个类)

最终的最终你会到达ServiceLoader它定义了一个变量

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

而且还有值了。

然后去mysql的jar包下面/META-INF/services/java.sql.Driver文件里面内容:

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

看到这,后面就不看代码了。

总结一下:

所谓的SPI机制,就是说把接口和实现分离(通常不在一个jar包里)

也就是写接口的人和写实现类的人不是同一个。

然后接口的提供方会获取对应的实现,调用对应的方法。而具体的实现还是在实现类里面

一方提供规范(接口)和逻辑(具体调用哪些方法),另一方根据规范实现相应接口和方法。调用提供方给的静态方法即可

猜你喜欢

转载自blog.csdn.net/qq_27605885/article/details/82383190