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

    一、什么是SPI(Service provider interface)

          是JDK内置的一种服务发现机制。例如有个接口,在运行时态给它添加实现,只需要添加一个实现就可以了。

    举个栗子:

    java.sql.Driver接口,其他不同厂商可以针对通一个接口做出不同的实现,mysql,orace等数据库都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。

    可以理解为JAVA SPI机制,可以让你提供一个标准服务接口,其他厂商、服务提供方。可以有不同实现。通过SPI的方式,对项目进行配置(项目代码的META-INF/services目录下的配置文件指定实现类的全路径名),源码框架即可找到实现类。继而执行对应的实现方法

    二、 SPI使用场景

    概括地说,适用于:调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略

    比较常见的例子:

    • 数据库驱动加载接口实现类的加载
      JDBC加载不同类型数据库的驱动
    • 日志门面接口实现类加载
      SLF4J加载不同提供商的日志实现类
    • Spring
      Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
    • Dubbo
      Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口

    举个例子:数据库DriverManager、Spring、ConfiguraleBeanFactory等都用到了SPI机制,这里DriverManager为例,看下如何实现的。

    DriverManager是JDC里管理和注册不通数据库Driver的工具类。针对一个数据库、会存在不同的数据库驱动实现。我们在使用特定驱动实现时,不希望修改现有代码,想通过简单的配置,就可以达到效果。在使用mysql驱动的时候,会有一个疑问,DriverManager如何获得驱动类的?实际上用的是Class.forName(“com.mysql.jdbc.Driver”)加载mysql驱动后,就会 执行其中的静态代码,把Driver注册到DriverManager中。

    来我们上源码:

     可以看到内部有个惊呆代码块loadInitalDriver方法,这个方法中用到了SPI工具类 ServiceLoader。

    上图截图可以的看到,先查找jdbc.drivers属性值,然后通过SPI机制查找驱动

    既然是一种机制,就需要遵循某种约定,SPI需要遵循如下约定:

    • 1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;(下图是mysql.jdbc.driver的例子)
    • 2、接口实现类所在的jar包放在主程序的classpath中;
    • 3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
    • 4、SPI的实现类必须携带一个不带参数的构造方法;

    5 总结

    优点
    使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

    相比使用提供接口jar包,供第三方服务模块实现接口的方式,SPI的方式使得源框架,不必关心接口的实现类的路径,可以不用通过下面的方式获取接口实现类:

    • 代码硬编码import 导入实现类
    • 指定类全路径反射获取:例如在JDBC4.0之前,JDBC中获取数据库驱动类需要通过Class.forName("com.mysql.jdbc.Driver"),类似语句先动态加载数据库相关的驱动,然后再进行获取连接等的操作
    • 第三方服务模块把接口实现类实例注册到指定地方,源框架从该处访问实例

    通过SPI的方式,第三方服务模块实现接口后,在第三方的项目代码的META-INF/services目录下的配置文件指定实现类的全路径名,源码框架即可找到实现类

    缺点

    • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
    • 多个并发多线程使用ServiceLoader类的实例是不安全的。

    DriverManager

    ==========================================================================           如果您觉得这篇文章对你有帮助,可以【关注我】或者【点赞】,希望我们一起在架构的路上,并肩齐行
    ==========================================================================
  • 相关阅读:
    Leetcode 811. Subdomain Visit Count
    Leetcode 70. Climbing Stairs
    Leetcode 509. Fibonacci Number
    Leetcode 771. Jewels and Stones
    Leetcode 217. Contains Duplicate
    MYSQL安装第三步报错
    .net 开发WEB程序
    JDK版本问题
    打开ECLIPSE 报failed to load the jni shared library
    ANSI_NULLS SQL语句
  • 原文地址:https://www.cnblogs.com/amberJava/p/12433279.html
Copyright © 2011-2022 走看看