• java中SPI机制 代码改变世界


    java SPI功能分享

    1.什么是SPI

    SPI,Service Provider Interface,是有java提供的一套用来被第三方实现或者扩展的API,本质是通过基于接口的编程+策略模式+配置文件实现动态加载。主要是被框架的开发人员使用,比如JDBC中驱驱动java.sql.Driver接口,不同的数据库厂商通过实现次接口完成对数据库的操作,mysql等数据库都有不同的实现类提供给用户,而Java的SPI机制可以为某个接口寻找具体的实现类。

    2.实现SPI的几个约定

    1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;

    2、接口实现类所在的jar包放在主程序的classpath中;

    3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;

    4、SPI的实现类必须携带一个不带参数的构造方法;

    3.SPI实现的例子

    步骤一:定义接口
    public interface LoadBalance {
    
        String selectServiceAddress(List<String> serviceAddresses);
    }
    
    步骤二:定义实现类
    public class RandomLoadBalance implements LoadBalance {
    
        @Override
        public String selectServiceAddress(List<String> serviceAddresses) {
            Random random = new Random();
            return serviceAddresses.get(random.nextInt(serviceAddresses.size()));
        }
    
    }
    
    步骤三:添加配置文件

    在resources文件目录下添加META-INF/services/目录,创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。内容如下:

    com.spi.javaspi.loadbalance.RandomLoadBalance
    
    步骤四:使用ServiceLoader加载实现类
    public static void main(String[] args) {
    
        ServiceLoader<LoadBalance> loadBalances = ServiceLoader.load(LoadBalance.class);
        Iterator<LoadBalance> matcherIter = loadBalances.iterator();
        while (matcherIter.hasNext()) {
            LoadBalance loadBalance = matcherIter.next();
            System.out.println(loadBalance.getClass().getName());
            System.out.println(loadBalance.selectServiceAddress(Arrays.asList("172.30.30.231", "172.30.30.232", "172.30.30.233")));
        }
    
    }
    

    4.JDBC中SPI使用分析

    JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用java语言编写的类和接口组成。

    JDBC操作数据库demo:
    Connection con;
    
        public Connection getConnection() {
            try {
                con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shop?characterEncoding=UTF-8", "root", "123456789");
                System.out.println("数据库连接成功");
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return con;
        }
    
        public static void main(String[] args) throws Exception {
            JDBCTest c = new JDBCTest();
            Connection connection = c.getConnection();
            PreparedStatement statement = connection.prepareStatement("select * from Product");
            ResultSet resultSet = statement.executeQuery();
            while (resultSet.next()) {
                String productName = resultSet.getString("product_name");
                System.out.println("productName: " + productName);
            }
        }
    
    相关类分析--DriverManager

    静态代码块:

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

    loadInitialDrivers方法:

    private static void loadInitialDrivers() {
        String drivers;
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                while (driversIterator.hasNext()) {
                    driversIterator.next();
                }
    
                return null;
            }
        });
    
        String[] driversList = drivers.split(":");
        for (String aDriver : driversList) {
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
    
        }
    }
    

    注册驱动方法:

    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    
    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            throw new NullPointerException();
        }
    }
    
    mysql中Driver实现类
    public class Driver extends NonRegisteringDriver implements java.sql.Driver {
        static {
            try {
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    
        public Driver() throws SQLException {
            // Required for Class.forName().newInstance()
        }
    }
    

    获取连接方法:

    private static Connection getConnection(
            String url, java.util.Properties info, Class<?> caller) throws SQLException {
    
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
        SQLException reason = null;
        for(DriverInfo aDriver : registeredDrivers) {
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
            }
        }
    }
    

    5.优缺点分析

    优点:通过SPI实现解耦,不需要改动源码就可以实现扩展

    缺点:JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现则初始化很耗时(比如静态代码块初始化耗时长),如果没 用上也加载,则浪费资源

    6.SPI机制的其他应用

    Dubbo、spring、log4j等框架也大量使用了SPI机制

  • 相关阅读:
    不怕路长,只怕心老——走在IT行业的路上
    python中 r'', b'', u'', f'' 的含义
    WSGI接口
    HTTP协议简介
    Flask中的Session
    一个 android 开机自动启动功能的例子
    遍历 JObject各属性(CSharp2)
    ASP.NET 伪随机数函数避免重复一例
    浏览器环境下 ES6 的 export, import 的用法举例
    在浏览器环境使用js库(不用require功能)
  • 原文地址:https://www.cnblogs.com/vitasyuan/p/15752695.html
Copyright © 2020-2023  润新知