Java的SPI机制入门

一:SPI简单介绍

最近在网上看到了java中SPI这个技术,据了解在JDBC,JNDI,日志门面,Dubbo等很多技术中都有使用,因此决定学习一下.

    SPI(service provider interface:服务提供者接口),为接口寻找服务实现类,编程时针对接口编程,由具体的服务提供商提供接口的实现(例如jdbc服务的具体实现可以是oracle,mysql等)
     java spi的具体使用如下 :
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过java.util.ServiceLoader解析该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。

二:SPI举例

示例工程结构如下:
在这里插入图片描述
具体代码如下:
服务接口:

package com.tdemo.api;

public interface TestSpi
{
	String sayHello();
}

接口实现类:

package com.tdemo.api.impl;

import com.tdemo.api.TestSpi;

public class TestSpiImpl implements TestSpi
{
	@Override
	public String sayHello()
	{
		return "HELLO";
	}
}

META-INF下的services文件夹下:
创建文件以服务接口的全类名命名:
com.tdemo.api.TestSpi
内部内容是接口的实现类全类名:
com.tdemo.api.impl.TestSpiImpl

测试代码:

package com.tdemo.test;

import com.tdemo.api.TestSpi;

import java.util.Iterator;
import java.util.ServiceLoader;

public class Test
{
	public static void main(String[] args)
	{
		ServiceLoader<TestSpi> service = ServiceLoader.load(TestSpi.class);
		Iterator<TestSpi> its =  service.iterator();
		while (its.hasNext()) {
			TestSpi operation = its.next();
			System.out.println(operation.sayHello());
		}
	}
}

执行结果:
在这里插入图片描述
说明实现类的确执行了

三:JDBC中如何使用SPI

mysql数据库实例中有一个库:test,其中有一个表:user

在这里插入图片描述
使用jdbc连接代码如下:

package com.tdemo.spiofjdbc.test;

import com.tdemo.spiofjdbc.model.User;

import java.sql.*;

public class Test
{
	public static void main(String[] args) throws SQLException
	{
		Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
		PreparedStatement preparedStatement = connection.prepareStatement("select * from user where id = 1");
		ResultSet resultSet = preparedStatement.executeQuery();
		while (resultSet != null && resultSet.next())
		{
			int age = resultSet.getInt("age");
			System.out.println(age);
		}
	}
}

注意到连接数据库的这行代码:

Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");

相对于以前的Class.forname(数据库驱动);简单的多
这里面就用到了SPI的机制,这是JDBC 4.0规范引入的(JDK6),这时候开始可以自动加载jdbc的驱动了

下面从源码的角度理解如何做到的:
DriverManager调用getConnection方法,所有会先加载DriverManager,静态变量初始化,静态代码块会执行:
在DriverManager中有以下静态代码块

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

静态变量:

 private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

loadInitialDrivers();方法如下:

private static void loadInitialDrivers() {
	    
		// 读取jvm的系统变量:jdbc.drivers,驱动的字符串名称
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        
		// SPI自动加载驱动的关键代码
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);
         
		// 通过Class.forName显示加载jvm系统配置的驱动:jdbc.drivers中的配置,可以配置多个,每个之间使用:分割
        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

先来看看SPI自动加载那段:

	// SPI自动加载驱动的关键代码
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                // 加载驱动前的准备:存储驱动的缓存以及迭代器
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                // 加载驱动
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

在ServiceLoader类中可以看到以下变量,加载服务的路径,后面会用到
在这里插入图片描述
ServiceLoader.load(Driver.class);中主要做了以下事情:

  1. 将Driver.class和系统类加载器初始化到以下迭代器中
    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;
  2. 清空以下缓存(存放驱动)
    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
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);
    }
private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

 public void reload() {
        providers.clear(); // 清空当前驱动缓存
        lookupIterator = new LazyIterator(service, loader); // 初始化迭代器
   }

以上可以说是准备工作,真正加载驱动的代码如下:
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}

public Iterator<S> iterator() {
        return new Iterator<S>() {
            // 初始驱动缓存肯定是空的
            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();
           
            // 判断是否包含下一个驱动
            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }

           // 获取下一个驱动
            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                return lookupIterator.next();
            }
           
            
            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

以下代码循环加载驱动:

while(driversIterator.hasNext()) {
driversIterator.next();
}

当执行driversIterator.hasNext()时
// 判断是否包含下一个驱动
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}

初始加载时knownProviders.hasNext()为false,所以执行lookupIterator.hasNext()

  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);
            }
        }

代理到lookupIterator的 hasNextService();这个地方会真正去加载驱动,初始时LazyIterator的nextName为空,configs(驱动名称枚举)为空,所以会执行 String fullName = PREFIX + service.getName();其中PREFIX 在ServiceLoader中:META-INF/services,显然此处fullName = META-INF/services/java.sql.Driver,执行完成后中存储了驱动的全名称,在下面这句
pending = parse(service, configs.nextElement());把所有驱动名加载到ArrayList中,通过迭代器访问

 private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

回到以下代码:在上面服务实现类名全都通过迭代器访问,hasNext返回true,访问一次,都将类型赋值给nextName,可以看到下文实际还是通过 c = Class.forName(cn, false, loader);来加载,然后创建实例存入缓存:
S p = service.cast(c.newInstance());
providers.put(cn, p);

private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                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 {
                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
        }

到此可以说驱动加载完成

总的来说:
这行代码初始化了 ServiceLoader内部的迭代器lookupIterator,它使用了适配器模式
ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
// 把访问代理到lookupIterator上
Iterator driversIterator = loadedDrivers.iterator();

try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
 }

lookupIterator的hasNext每执行一次就是读取一个驱动名赋值给nextName,并返回true,接下来
lookupIterator的next就在加载每一个nextName对应的驱动,并存入缓存:
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章