SPI(Service Provider Interface)

ServiceLoad中的spi

1、简介
  • JDK1.6引入的特性,用来实现SPI(Service Provider Interface),一种服务发现机制。
2、JDBC举例
2.1、引入mysql依赖jar
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

2、前置了解:
  • JDBC:Java DataBase Connectivity,是sun公司提供的一套操作数据库的标准规范
  • java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口(规范)
  • 这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现(mysql、Oracle)
  • mysql的Driver也需要实现这个接口,以下是mysql的具体实现类
package com.mysql.jdbc;
import java.sql.DriverManager;

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!");
        }
    }
}
  • mysql的jar中把具体实现的类Driver,放在META-INF/services中接口全路径file即java.sql.Driver中,内容为具体的实现类全路径com.mysql.jdbc.Driver

2.3、数据库连接过程(这里重点分析一中的spi)
        String url = "jdbc:mysql://localhost:3306/test?user=root&password=root";  //定义连接数据库的url
        //一、获取mysql的数据库连接对象
        Connection conn = DriverManager.getConnection(url);
        //二、获取SQL语句执行对象
        Statement statement = conn.createStatement();
        //三、执行SQL语句
        int result = statement.executeUpdate("sql语句");
2.4、分析过程

将服务中所有.jar包下META-INF/services/java.sql.Driver文件中的实现类(eg:mysql的com.mysql.jdbc.Driver)加载到内存list中,并在getConnection中从list中获取实例对象

public class DriverManager {
    static {
        loadInitialDrivers();
    }
    
    public static Connection getConnection(String url)
        ///
    }
 }

2.4.1、通过spi,获取所有Driver接口实现类对象,放入list

1)、DriverManager.getConnection(url),因为getConnection是静态方法,会触发DriverManager类的首次主动使用,会调用()即调用static{}中loadInitialDrivers

private static void loadInitialDrivers() {
        // 一、load
		ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();
        // 二、hasNext
         while(driversIterator.hasNext()) {
             // 三、next
             driversIterator.next();
         }
  }

2)、load():指定上下文类加载器加载作为Driver接口的具体实现类com.mysql.jdbc.Driver的加载器

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();//上下文加载器,去加载Deiver接口的实现类
        return ServiceLoader.load(service, cl);
    }
打破双亲委派机制:

SPI接口实现类的加载需要破坏双亲委派模型。

  • java.sql.Driver是由根类加载器加载(因为其在rt.jar下)

  • 而不同厂商具体的实现(mysql、Oracle)的jar放在classpath(com|META-INF)下,根类加载器无法加载classpath路径下的类

  • 通过Thread.currentThread().setContextClassLoader,设置系统类加载器来加载(线程上下文中默认存放的系统类加载器)

3)、hasNext():按行读取所有.jar中含有"META-INF/services/com.sql.Driver"的file文件内容,将实现类全路径加入Iterator

        private boolean hasNextService() {
            //1.首次加载为null
            if (configs == null) {
                    // 2.这里的fullName = "META-INF/services/" + com.sql.Driver
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        // 3.获取url(不重要)
                        configs = loader.getResources(fullName);
               
            }
            while ((pending == null) || !pending.hasNext()) {
                // 4.会读取所有.jar,含有"META-INF/services/com.sql.Driver"的file文件
                // 一行一行的读取file的内容,比如读取com.msql.jdbc.Driver,将Diver接口所有的实现类的全路径放Iterator
                Iterator<String> pending = parse(service, configs.nextElement());//parseLine(service,names)
            }
            nextName = pending.next();
            return true;
        }

4)、next():创建Driver接口实现类的实例对象,并将实例对象放入list,便于后续getConnection时从list中获取实例对象

      private S nextService() {
			// 1.Diver接口实现类的全路径,比如com.mysql.jdbc.Driver
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                // 2.使用上下文加载器,创建class(com.mysql.jdbc.Driver)类对象
                c = Class.forName(cn, false, loader);
            } 
            
            try {
                // 3.生成Driver实现类的实例对象(重要)
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } 
        }


// 这里特别重要的一个点是c.newInstance(),即对实现类Diver创建实例
根据类的首次主动使用原则,会触发com.mysql.jdbc.Driver类的static{}
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } 
    }
}

    
//registerDriver方法,会将com.mysql.jdbc.Driver实例,加入list中,便于后续getConnection的获取
public static synchronized void registerDriver(java.sql.Driver driver){

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            //CopyOnWriteArrayList<DriverInfo> registeredDrivers 
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } 
}
  1. 4.2 getConnection
    public static Connection getConnection(String url)
        return (getConnection(url, info, Reflection.getCallerClass()));
    }
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
    
        // CopyOnWriteArrayList<DriverInfo> registeredDrivers
        // 这里遍历上述2.4.1中存入list的所有Driver接口的实现类对象,然后connect,这里以com.mysql.jdbc.Driver的connect
        // 为例
        for(DriverInfo aDriver : registeredDrivers) {
            Connection con = aDriver.driver.connect(url, info);
            if (con != null) {
               println("getConnection returning " + aDriver.driver.getClass().getName());
               return (con);
             }      
 }
public class NoRegisterDriver{
   public Connection connect(String url, Properties info) throws SQLException {
        
        // 1.一般我们都会使用.properties将数据库连接的url、port、user、password等属性放在文件中使用Properties加载
        Properties props = null;
         // 2.创建mysql的数据库连接(localhost即ip地址信息、port端口信息等,都可以从String url = "jdbc:mysql://localhost:3306/test?user=root&password=root"中解析)
        com.mysql.jdbc.Connection newConn = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);
                return newConn;
            }
        }
    }
}


    protected static Connection getInstance(String hostToConnectTo, int portToConnectTo, Properties info, 				String databaseToConnectTo, String url)  {
        // 底层还是通过反射,Constructor ctor.newInstance(args)创建的JDBC4对应的mysql的con对象
    }     

至此,就得到了mysql数据库的con连接对象

3、自定义操作

3.1 定义client模块,内含自定义接口Myservice以及其实现类MyServiceImpl

public interface MyService {
    public void show();
}




public class MyServiceImpl implements MyService {
    @Override
    public void show() {
        System.out.println("load MyServiceImpl spi");
    }
}

3.2 将自定义接口实现类全路径,作为内容写在清单文件file(自定义接口全路径)中

  • 创建清单文件:resources/META-INF/services/创建file,file的名称为自定义接口的全路径:com.mjp.service.MyService
  • file清单文件内容自定义接口实现类的全路径:com.mjp.service.impl.MyServiceImpl(可以在多行写多个实现类)
  • install模块client生成jar,后续在service中pom引入此jar

3.3 定义service模块

  • pom中依赖client模块
    <dependencies>
        <dependency>
            <groupId>CodeBetter</groupId>
            <artifactId>com.mjp.client</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
  • 编写ServiceLoader
    public static void main(String[] args) {
        ServiceLoader<MyService> services = ServiceLoader.load(MyService.class);
        for (MyService service : services) {
            service.show();//load MyServiceImpl spi
        }
    }

在这里插入图片描述

4、作用

spi服务发现机制,更具有插拔性。

  • 每当接口有新的实现类时,只要实现类遵循file文件的存放目录以及命名和内容,则ServiceLoader.load就帮你把所有的实现类都加载到ServiceLoader<接口> loadedServices中,程序员无需感知,可以直接在代码中使用新增的实现类对象,而不是在硬编程的方式,自己再new实现类对象。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值