zoukankan      html  css  js  c++  java
  • 组件化框架设计之Java SPI机制(三)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

    本篇文章将从深入理解java SPI机制来介绍组件化框架设计:

    一、SPI机制定义

    SPI机制(Service Provider Interface)其实源自服务提供者框架(Service Provider Framework,参考【EffectiveJava】page6),是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了spi接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔。

    二、典型实例:jdbc的设计

    通常各大厂商(如Mysql、Oracle)会根据一个统一的规范(java.sql.Driver)开发各自的驱动实现逻辑。客户端使用jdbc时不需要去改变代码,直接引入不同的spi接口服务即可。
    Mysql的则是com.mysql.jdbc.Drive,Oracle则是oracle.jdbc.driver.OracleDriver。

     
    19956127-8d5f20dffb566878.png
     

    伪代码如下:

        //注:从jdbc4.0之后无需这个操作,spi机制会自动找到相关的驱动实现
        //Class.forName(driver);
        
        //1.getConnection()方法,连接MySQL数据库。有可能注册了多个Driver,这里通过遍历成功连接后返回。
        con = DriverManager.getConnection(mysqlUrl,user,password);
        //2.创建statement类对象,用来执行SQL语句!!
        Statement statement = con.createStatement();
        //3.ResultSet类,用来存放获取的结果集!!
        ResultSet rs = statement.executeQuery(sql);
    

    jdbc连接源码分析

    1. java.sql.DriverManager静态块初始执行,其中使用spi机制加载jdbc具体实现
     //java.sql.DriverManager.java   
     //当调用DriverManager.getConnection(..)时,static会在getConnection(..)执行之前被触发执行
        /**
         * Load the initial JDBC drivers by checking the System property
         * jdbc.properties and then use the {@code ServiceLoader} mechanism
         */
        static {
            loadInitialDrivers();
            println("JDBC DriverManager initialized");
        }
    

    2.loadInitialDrivers()中完成了引入的数据库驱动的查找以及载入,本示例只引入了oracle厂商的mysql,我们具体看看。

    //java.util.serviceLoader.java

       private static void loadInitialDrivers() {
            String drivers;
            try {
                drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                    public String run() {
                    //使用系统变量方式加载
                        return System.getProperty("jdbc.drivers");
                    }
                });
            } catch (Exception ex) {
                drivers = null;
            }
            //如果spi 存在将使用spi方式完成提供的Driver的加载
            // If the driver is packaged as a Service Provider, load it.
            // Get all the drivers through the classloader
            // exposed as a java.sql.Driver.class service.
            // ServiceLoader.load() replaces the sun.misc.Providers()
    
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
    //查找具体的provider,就是在META-INF/services/***.Driver文件中查找具体的实现。
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                    Iterator<Driver> driversIterator = loadedDrivers.iterator();
    
                    /* Load these drivers, so that they can be instantiated.
                     * It may be the case that the driver class may not be there
                     * i.e. there may be a packaged driver with the service class
                     * as implementation of java.sql.Driver but the actual class
                     * may be missing. In that case a java.util.ServiceConfigurationError
                     * will be thrown at runtime by the VM trying to locate
                     * and load the service.
                     *
                     * Adding a try catch block to catch those runtime errors
                     * if driver not available in classpath but it's
                     * packaged as service and that service is there in classpath.
                     */
                     //查找具体的实现类的全限定名称
                    try{
                        while(driversIterator.hasNext()) {
                            driversIterator.next();//加载并初始化实现类
                        }
                    } catch(Throwable t) {
                    // Do nothing
                    }
                    return null;
                }
            });
    
            println("DriverManager.initialize: jdbc.drivers = " + drivers);
    
            if (drivers == null || drivers.equals("")) {
                return;
            }
            String[] driversList = drivers.split(":");
    ....
            }
        }
    

    3.java.util.ServiceLoader 加载spi实现类.

    上一步的核心代码如下,我们接着分析:

    
    //java.util.serviceLoader.java
    
    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    Iterator<Driver> driversIterator = loadedDrivers.iterator();
    try{
      //查找具体的实现类的全限定名称
         while(driversIterator.hasNext()) {
         //加载并初始化实现
             driversIterator.next();
         }
     } catch(Throwable t) {
     // Do nothing
     }
    

    主要是通过ServiceLoader来完成的,我们按照执行顺序来看看ServiceLoader实现:

    //初始化一个ServiceLoader,load参数分别是需要加载的接口class对象,当前类加载器
        public static <S> ServiceLoader<S> load(Class<S> service) {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            return ServiceLoader.load(service, cl);
        }
        public static <S> ServiceLoader<S> load(Class<S> service,
                                                ClassLoader loader)
        {
            return new ServiceLoader<>(service, loader);
        }
    

    遍历所有存在的service实现

            public boolean hasNext() {
                if (acc == null) {
                    return hasNextService();
                } else {
                    PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                        public Boolean run() { return hasNextService(); }
                    };
                    return AccessController.doPrivileged(action, acc);
                }
            }
    
        //写死的一个目录
           private static final String PREFIX = "META-INF/services/";
    
           private boolean hasNextService() {
                if (nextName != null) {
                    return true;
                }
                if (configs == null) {
                    try {
                        String fullName = PREFIX + service.getName();
                        //通过相对路径读取classpath中META-INF目录的文件,也就是读取服务提供者的实现类全限定名
                        if (loader == null)
                            configs = ClassLoader.getSystemResources(fullName);
                        else
                            configs = loader.getResources(fullName);
                    } catch (IOException x) {
                        fail(service, "Error locating configuration files", x);
                    }
                }
                //判断是否读取到实现类全限定名,比如mysql的“com.mysql.jdbc.Driver
    ”
                while ((pending == null) || !pending.hasNext()) {
                    if (!configs.hasMoreElements()) {
                        return false;
                    }
                    pending = parse(service, configs.nextElement());
                }
                nextName = pending.next();//nextName保存,后续初始化实现类使用
                return true;//查到了 返回true,接着调用next()
            }
    
            public S next() {
                if (acc == null) {//用来判断serviceLoader对象是否完成初始化
                    return nextService();
                } else {
                    PrivilegedAction<S> action = new PrivilegedAction<S>() {
                        public S run() { return nextService(); }
                    };
                    return AccessController.doPrivileged(action, acc);
                }
            }
          private S nextService() {
                if (!hasNextService())
                    throw new NoSuchElementException();
                String cn = nextName;//上一步找到的服务实现者全限定名
                nextName = null;
                Class<?> c = null;
                try {
                //加载字节码返回class对象.但并不去初始化(换句话就是说不去执行这个类中的static块与static变量初始化)
                //
                    c = Class.forName(cn, false, loader);
                } catch (ClassNotFoundException x) {
                    fail(service,
                         "Provider " + cn + " not found");
                }
                if (!service.isAssignableFrom(c)) {
                    fail(service,
                         "Provider " + cn  + " not a subtype");
                }
                try {
                    //初始化这个实现类.将会通过static块的方式触发实现类注册到DriverManager(其中组合了一个CopyOnWriteArrayList的registeredDrivers成员变量)中
                    S p = service.cast(c.newInstance());
                    providers.put(cn, p);//本地缓存 (全限定名,实现类对象)
                    return p;
                } catch (Throwable x) {
                    fail(service,
                         "Provider " + cn + " could not be instantiated",
                         x);
                }
                throw new Error();          // This cannot happen
            }
    

    上一步中,Sp = service.cast(c.newInstance()) 将会导致具体实现者的初始化,比如mysqlJDBC,会触发如下代码:

    //com.mysql.jdbc.Driver.java
    ......
        private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    ......
    
        static {
            try {
                 //并发安全的想一个copyOnWriteList中方
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    

    4.最终Driver全部注册并初始化完毕,开始执行DriverManager.getConnection(url, “root”, “root”)方法并返回。

    使用实例
    四个项目:spiInterface、spiA、spiB、spiDemo

    spiInterface中定义了一个com.zs.IOperation接口。

    spiA、spiB均是这个接口的实现类,服务提供者。

    spiDemo作为客户端,引入spiA或者spiB依赖,面向接口编程,通过spi的方式获取具体实现者并执行接口方法。

    ├─spiA
    │  └─src
    │      ├─main
    │      │  ├─java
    │      │  │  └─com
    │      │  │      └─zs
    │      │  ├─resources
    │      │  │  └─META-INF
    │      │  │      └─services
    │      │  └─webapp
    │      │      └─WEB-INF
    │      └─test
    │          └─java
    ├─spiB
    │  └─src
    │      ├─main
    │      │  ├─java
    │      │  │  └─com
    │      │  │      └─zs
    │      │  ├─resources
    │      │  │  └─META-INF
    │      │  │      └─services
    │      │  └─webapp
    │      │      └─WEB-INF
    │      └─test
    │          └─java
    ├─spiDemo
    │  └─src
    │      ├─main
    │      │  ├─java
    │      │  │  └─com
    │      │  │      └─zs
    │      │  ├─resources
    │      │  └─webapp
    │      │      └─WEB-INF
    │      └─test
    │          └─java
    └─spiInterface
        └─src
            ├─main
            │  ├─java
            │  │  └─com
            │  │      └─zs
            │  ├─resources
            │  └─webapp
            │      └─WEB-INF
            └─test
                └─java
                    └─spiInterface
    

    spiDemo

    
    package com.zs;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Iterator;
    import java.util.ServiceLoader;
    
    public class Launcher {
    
        public static void main(String[] args) throws Exception {
    //      jdbcTest();
            showSpiPlugins();
            
        }
        private static void jdbcTest() throws SQLException {
            String url = "jdbc:mysql://localhost:3306/test";
            Connection conn = DriverManager.getConnection(url, "root", "root");
            Statement statement = conn.createStatement();
            ResultSet set = statement.executeQuery("select * from test.user");
            while (set.next()) {
                System.out.println(set.getLong("id"));
                System.out.println(set.getString("userName"));
                System.out.println(set.getInt("age"));
            }
        }
        private static void showSpiPlugins() {
            ServiceLoader<IOperation> operations = ServiceLoader.load(IOperation.class);
            Iterator<IOperation> operationIterator = operations.iterator();
            
            while (operationIterator.hasNext()) {
                IOperation operation = operationIterator.next();
                System.out.println(operation.operation(6, 3));
            }
        }
    }
    

    SPI示例 完整代码。
    原文链接https://blog.csdn.net/lemon89/article/details/79189475
    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

  • 相关阅读:
    hdu 1269 迷宫城堡 (并查集)
    hdu 1272 小希的迷宫 (深搜)
    hdu 1026 Ignatius and the Princess I (深搜)
    hdu 1099 Lottery
    hdu 1068 Girls and Boys (二分匹配)
    几个基础数位DP(hdu 2089,hdu 3555,uestc 1307 windy 数)
    hdu 1072 Nightmare (广搜)
    hdu 1398 Square Coins (母函数)
    hdu 1253 胜利大逃亡 (深搜)
    hdu 1115 Lifting the Stone (求重心)
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11983844.html
Copyright © 2011-2022 走看看