文章目录

  • Java SPI
  • SPI源码分析
  • JDBC SPI


Java SPI

最近了解到Dubbo / SpringBoot / Nacos / JDBC都使用到了SPI机制,各位大咖都用了,必须好好看一看。

SPI(Service Provider Interface)是JDK内置的一种提供服务发现的机制。
在java中根据一个子类获取其父类或接口信息非常方便,但是根据一个接口获取该接口的所有实现类却没那么容易。
SPI便是解决接口获取实现类的很好的解决方案,我们定义一个接口,
第三方服务提供者可以实现我们的接口,并根据SPI约定在 META-INF/services/ 目录里创建一个以服务接口命名的文件,该文件里写的就是实现该服务接口的具体实现类。我们便可以调用第三方提供的服务。

动手演示下,SPI的使用。

public interface Play {
    List<String> play(String keyword);
}
public class IPadPlay implements Play {
    @Override
    public List<String> play(String keyword) {
        System.out.println("Play games with IPad. keyword:" + keyword);
        return null;
    }
}
public class IPhonePlay implements Play {
    @Override
    public List<String> play(String keyword) {
        System.out.println("Play games with IPhone. keyword:" + keyword);
        return null;
    }
}
public class MainTest {
    public static void main(String[] args) {
        System.out.println("Hi, Every");
        ServiceLoader<Play> s = ServiceLoader.load(Play.class);
        Iterator<Play> iterator = s.iterator();
        while (iterator.hasNext()) {
            Play search = iterator.next();
            search.play("Ready, GO!");
        }
    }
}

META-INF/services目录下,有2种方式可以存放具体实现类

方式一,建立Play目录(注意是Play接口的全限定名)

然后,Play目录下,建立IPadPlay和IPhonePlay2个文件(2个也是全限定名),文件内容为空,如下图:

java screen 显示界面 java display_SPI


方式二,META-INF/services目录下,直接建com.example.springdemo.java.spi1.Play1文件,这个文件里的内容为:

java screen 显示界面 java display_java screen 显示界面_02


完成以上步骤,程序就可以执行了,输出结果

Hi, Every
Play games with IPad. keyword:Ready, GO!
Play games with IPhone. keyword:Ready, GO!

如此,对于扩展非常方便。

SPI源码分析

ServiceLoader.load(…)的重点是清空了providers,并new了一个LazyIterator

public static <S> ServiceLoader<S> load(Class<S> service) {
	ClassLoader cl = Thread.currentThread().getContextClassLoader();
	return ServiceLoader.load(service, cl);
}
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);
}

s.iterator()方法

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

	};
}

接下来的iterator.hasNext()和iterator.next()方法才是重点
iterator.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);
	}
}

private static final String PREFIX = "META-INF/services/";

private boolean hasNextService() {
	if (nextName != null) {
		return true;
	}
	if (configs == null) {
		try {
			// 加载classpath:META-INF/services/xxx
			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;
		}
		// 解析文件里的值,多行为多个值,返回一个Iterator
		pending = parse(service, configs.nextElement());
	}
	nextName = pending.next();
	return true;
}

iterator.next() // 具体实现实例化

public S next() {
	if (acc == null) {
		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和构造函数
		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
}

JDBC SPI

日常,我们手动连接数据库并获取结果的伪代码如下:

// 注册 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");

// 打开链接
System.out.println("连接数据库...");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/xx","1","2");

// 执行查询
System.out.println(" 实例化Statement对象...");
stmt = conn.createStatement();
String sql;
sql = "SELECT id FROM table1";
ResultSet rs = stmt.executeQuery(sql);

以上这段代码,是怎么利用SPI的呢?
在执行DriverManager.getConnection(…)时,会先执行DriverManager类中的static块

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

在loadInitialDrivers()中,我们重点关注

private static void loadInitialDrivers() {
	// ...
	// 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() {
			
			// 这里是不是很熟悉,ServiceLoader.load就是我们上面例子中的用法
			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);

	//...
}

我们之前在讲nextService()方法时,Sp = service.cast(c.newInstance()) 会初始化具体实现者,在mysqlJDBC中,会触发以下代码

// com.mysql.cj.jdbc.Driver
static {
	try {
		DriverManager.registerDriver(new Driver());
	} catch (SQLException var1) {
		throw new RuntimeException("Can't register driver!");
	}
}

如此,Driver全部注册并初始化完毕,开始执行DriverManager.getConnection(url, “1”, “2”)方法并返回。