ServiceLoader
的load
方法进入初始化,再通过迭代器加载所有的实现类。ServiceLoader
中的属性和内部类load
方法的源码ServiceLoader
实现了Iterable接口,其唯一的遍历方式也是通过Iterator迭代器,即其内部类LazyIterator
,每次的next()
操作会初始化一个实现类并生成一个实例SPI 全称是 Service Provider Interface ,中文意思是服务提供商接口。起初是给服务商做插件开发的,是为了满足随时可插拔的需求而诞生的一种机制。
Java SPI 是一种本地服务发现机制。使用的是策略模式,一个接口多种实现,使用时根据标准化接口和配置文件灵活生成实现类,使得接口易扩展。
举例:
//接口及对应方法
public interface PrintService {
void print();
}
//实现类1
public class PrintServiceImpl1 implements PrintService {
@Override
public void print() {
System.out.println("This is PrintServiceImpl-1");
}
}
//实现类2
public class PrintServiceImpl2 implements PrintService {
@Override
public void print() {
System.out.println("This is PrintServiceImpl-2");
}
}
com.example.demo.impl.PrintServiceImpl2
com.example.demo.impl.PrintServiceImpl1
//测试代码
public class DemoApplication {
public static void main(String[] args) {
ServiceLoader<PrintService>services=ServiceLoader.load(PrintService.class);
for(PrintService printService:services){
printService.print();
}
}
}
ServiceLoader
的load
方法进入初始化,再通过迭代器加载所有的实现类。ServiceLoader
中的属性和内部类public final class ServiceLoader<S> implements Iterable<S>{
//接口全限定名文件的路径前缀
private static final String PREFIX = "META-INF/services/";
//需要加载的接口
// The class or interface representing the service being loaded
private final Class<S> service;
//用于加载接口实现类的类加载器
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
//创建serviceloader时获取的访问控制上下文
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
//服务缓存池,key:接口实现类的全限定名,value:实现类
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
//实现类的迭代器,延迟加载
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
...省略已实现方法
// Private inner class implementing fully-lazy provider lookup
//
private class LazyIterator implements Iterator<S>{
//接口类型
Class<S> service;
//类加载器
ClassLoader loader;
//META-INF/services/内配置文件的URL集合
Enumeration<URL> configs = null;
//需加载的实现类的全限定名集合
Iterator<String> pending = null;
//下一个实现类的全限定名,用于迭代器延迟加载下一个实现类
String nextName = null;
...省略已实现方法
}
}
load
方法的源码ServiceLoader.load(Class)
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
//进入load的重载方法
return ServiceLoader.load(service, cl);
}
进入load
重载方法
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
//按接口和类加载器生成新的对象
return new ServiceLoader<>(service, loader);
}
ServiceLoader
类构造器
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();
}
reload
方法
public void reload() {
//服务缓存池清空
providers.clear();
//初始化迭代器
lookupIterator = new LazyIterator(service, loader);
}
LazyIterator
类构造器
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
由上可见load方法并未加载具体的实现类,只对ServiceLoad类进行了初始化。接口实现类在使用迭代器遍历时才开始加载。
ServiceLoader
实现了Iterable接口,其唯一的遍历方式也是通过Iterator迭代器,即其内部类LazyIterator
,每次的next()
操作会初始化一个实现类并生成一个实例迭代器从LazyIterator.next()
进入,调用的是LazyIterator.nextService()
private S nextService() {
//判断是否还有下一个类需要加载
if (!hasNextService())
throw new NoSuchElementException();
//cn是文件中实现类的全限定名,如com.example.demo.impl.PrintServiceImpl2
String cn = nextName;
//nextName置空会在hasNextService()中重新分配下一个全限定名
nextName = null;
Class<?> c = null;
try {
//通过反射来生成接口实现类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
//校验实现类c是否 和接口相同或是该类的子类
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//生成实现类的一个实例,并对实例进行强制类型转换
S p = service.cast(c.newInstance());
//放入缓存池,key:实现类全限定名 value:一个实例
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
在进入nextService()
前会先执行hasNextService()
方法,生成nextService()
中所需的实现类全限定名nextName
private boolean hasNextService() {
//全限定名不为空说明还未生产对应类
if (nextName != null) {
return true;
}
//若配置文件URL未获取则去获取所有URL
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;
}
parse(Class> service, URL u)
方法会解析URL对应的资源文件,对每一行进行解析
private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
//字符流读取文件
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
//解析每一行
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
parseLine(Class> service, URL u, BufferedReader r, int lc, List names)
解析一行字符串并加入全限定名集合
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names)
throws IOException, ServiceConfigurationError
{
//读取一行
String ln = r.readLine();
if (ln == null) {
return -1;
}
//#后面为注释,只截取#前的字符串
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
//检查是否存在不合法的符号
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
//检查首字母是否合法
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
//判断其他字符是否合法
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
//不存在则加入全限定名的缓存池
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}
Java SPI 是通过反射生成文件中全限定名对应的类,并交由接口或父类管理。它能使第三方插件和业务代码分离。其虽然采用了延迟加载的形式,但使用时会对文件进行全量遍历来生成所有实现类及其对应实例,比较耗费资源。
如果有疑问,欢迎评论~
如果成功解决了你的问题,点个赞再走吖~