java中SPI机制

2022-01-05 14:16:24 浏览数 (1)

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实现的例子

步骤一:定义接口
代码语言:javascript复制
public interface LoadBalance {

    String selectServiceAddress(List<String> serviceAddresses);
}
步骤二:定义实现类
代码语言:javascript复制
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/目录,创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。内容如下:

代码语言:javascript复制
com.spi.javaspi.loadbalance.RandomLoadBalance
步骤四:使用ServiceLoader加载实现类
代码语言:javascript复制
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:
代码语言:javascript复制
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

静态代码块:

代码语言:javascript复制
static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

loadInitialDrivers方法:

代码语言:javascript复制
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());

    }
}

注册驱动方法:

代码语言:javascript复制
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实现类
代码语言:javascript复制
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()
    }
}

获取连接方法:

代码语言:javascript复制
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机制

0 人点赞