• Java SPI的本质


    SPI的目的

    让调用者调用更加方便

    SPI和Interface的本质区别

    1 SPI的接口定义和接口实现是分开的(不同jar包),SPI就是一种调用场景(没别的)

    2 Interface的接口定义和接口实现都在服务提供方

    SPI如何实现

    1 底层使用JDK的ServiceLoader

    (1)配置文件:META-INF/services/接口名称

    (2)配置文件内容为具体的实现类的全限定名

    2 具体业务逻辑在SPI接口定义方实现

     没有SPI的时候(以com.mysql.jdbc.Driver为例)

    1 没有SPI:需要手动实例化所需的Driver,新加入一种实现就需要再写一遍

    2 有SPI:

    Class.forName(jdbcClass);
    Connection conn = DriverManager.getConnection(jdbcUrl, userName, password);

    DriverManager源码

    (1)用ServiceLoader加载所有Driver类,调用load方法只是清空了所有的providers

    1. public class DriverManager {
    2. static {
    3. loadInitialDrivers();
    4. println("JDBC DriverManager initialized");
    5. }
    6. private static void loadInitialDrivers() {
    7. ...
    8. ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
    9. Iterator driversIterator = loadedDrivers.iterator();
    10. try{
    11. while(driversIterator.hasNext()) {
    12. // 调用ServiceLoader的next()
    13. driversIterator.next();
    14. }
    15. } catch(Throwable t) {
    16. // Do nothing
    17. }
    18. ...
    19. }
    20. }

    (2)核心代码在ServiceLoader中的hasNextService和nextService。

         a) hasNextService会调用系统classLoader(AppClassLoader)去所有classpath中d的META-INF/services/ + 接口名文件下找到类名

         b) nextService会调用Class.forName加载类

    1. public final class ServiceLoader
    2. implements Iterable {
    3. public S next() {
    4. if (acc == null) {
    5. return nextService();
    6. } else {
    7. PrivilegedAction action = new PrivilegedAction() {
    8. public S run() { return nextService(); }
    9. };
    10. return AccessController.doPrivileged(action, acc);
    11. }
    12. }
    13. private boolean hasNextService() {
    14. if (nextName != null) {
    15. return true;
    16. }
    17. if (configs == null) {
    18. try {
    19. String fullName = PREFIX + service.getName();
    20. if (loader == null)
    21. configs = ClassLoader.getSystemResources(fullName);
    22. else
    23. configs = loader.getResources(fullName);
    24. } catch (IOException x) {
    25. fail(service, "Error locating configuration files", x);
    26. }
    27. }
    28. while ((pending == null) || !pending.hasNext()) {
    29. if (!configs.hasMoreElements()) {
    30. return false;
    31. }
    32. pending = parse(service, configs.nextElement());
    33. }
    34. nextName = pending.next();
    35. return true;
    36. }
    37. private S nextService() {
    38. if (!hasNextService())
    39. throw new NoSuchElementException();
    40. String cn = nextName;
    41. nextName = null;
    42. Class c = null;
    43. try {
    44. c = Class.forName(cn, false, loader);
    45. } catch (ClassNotFoundException x) {
    46. fail(service,
    47. "Provider " + cn + " not found");
    48. }
    49. if (!service.isAssignableFrom(c)) {
    50. fail(service,
    51. "Provider " + cn + " not a subtype");
    52. }
    53. try {
    54. S p = service.cast(c.newInstance());
    55. providers.put(cn, p);
    56. return p;
    57. } catch (Throwable x) {
    58. fail(service,
    59. "Provider " + cn + " could not be instantiated",
    60. x);
    61. }
    62. throw new Error(); // This cannot happen
    63. }
    64. }

     

  • 相关阅读:
    ctfshow——54命令执行,需要严格的过滤
    mac electron引入原生SDK
    [Power Query] 添加列
    如何在 Ubuntu 上安装 EMQX MQTT 服务器
    React <> </>的用法
    MQTT 资源
    MySQL 深度分页性能急剧下降,该如何优化?
    VMware Workstation里面安装ubuntu20.04的流程
    Nginx替代产品-Tengine健康检测
    高性能定时器实现
  • 原文地址:https://blog.csdn.net/wx19950101/article/details/121796435