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机制