SPI机制都不了解,还学什么Java?

前言

SPI全称叫Service Provider Interface,在Java 6时引入的这个功能,用于加载所给出的接口相匹配的实现, 就是可以在运行时动态给一个接口添加实现,只需要在src/META-INF/services/目录下建立一个文件,文件名是接口的全限定名,文件的内容是由多行具体的实现类全名组成。

简单的说SPI就是为某个接口寻找实现的机制,他的核心便是解耦合,通过SPI机制,将实现类隐藏在接口后面,根据需要寻找服务实现。

有不少框架用它来做服务的扩展发现,如果阅读过一些框架的源码,就会发现它会无处不在,比如我们经常使用的spring框架,spring-web包下就使用这个机制。
在这里插入图片描述

Effective Java中也提到SPI是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了SPI接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔。

SPI示例

首先定义一个接口,并写一个或多个接口的实现类。

public interface IPrint {
        public void print(String msg);
}

简单打印:

public class BasePrint implements  IPrint {
    @Override
    public void print(String msg) {
        System.out.println(msg);
    }
}

打印调用者的类名、方法名:

public class SeniorPrint implements IPrint {
    @Override
    public void print(String msg) {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        StackTraceElement stackTraceElement = stackTrace[stackTrace.length - 1];
        System.out.println(String.join("===>",stackTraceElement.getClassName(),stackTraceElement.getMethodName(),msg));
    }
}

新建META-INF/services文件夹,在其中创建这个接口的全限定名文件,文件中是其接口的一个或多个实现类。

在这里插入图片描述

在这里插入图片描述

最后通过ServiceLoader加载出所有IPrint的实现类。

public class SpiMain {
    public static void main(String[] args) {
        ServiceLoader<IPrint> shouts = ServiceLoader.load(IPrint.class);
        IPrint print =null;
        for (IPrint item : shouts) {
            print=item;
        }
        if (print!=null){
            print.print("demo");
        }
    }
}

输出:

com.spi.SpiMain===>main===>demo

JDBC中是如何使用的?

在以前,我们通常会先使用Class.forName("com.mysql.cj.jdbc.Driver")加载数据库的驱动,然后再进行获取连接,而在后续的版本中不需要用Class.forName(“com.mysql.jdbc.Driver”)来加载驱动,直接获取连接就可以了,这种方式就是使用了SPI扩展机制,(具体在那个版本中,我并没有去查阅)。

public class SpiMain {
    public static void main(String[] args) {
        try {
            Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_school", "root", "hxl495594..");
            System.out.println(connection);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

输出:

com.mysql.cj.jdbc.ConnectionImpl@281e3708

来看一下具体的源码,首先是DriverManager的静态代码块,调用了loadInitialDrivers方法,用来加载驱动。

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

在loadInitialDrivers方法下就可以看出,使用ServiceLoader.load(Driver.class)加载Driver的实现类,然后通过 driversIterator.next();加载其实现类。

 AccessController.doPrivileged(new PrivilegedAction<Void>() {
     public Void run() {
         ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
         Iterator<Driver> driversIterator = loadedDrivers.iterator();
         try{
             while(driversIterator.hasNext()) {
                 driversIterator.next();
             }
         } catch(Throwable t) {
         }
         return null;
     }
 });

这里的Iterator其实是ServiceLoader内部类下的LazyIterator实例,通过层层调用,最终会调用Class.forName

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

而在mysql的jar包下必定会存有java.sql.Driver文件,内容是就是其实现类。
在这里插入图片描述
类加载后会自动执行静态方法,调用DriverManager类的注册方法registeredDrivers向集合中加入实例,之后我们就可以使用DriverManager.getConnection获取了。

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!");
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值