深入了解Java的ServiceLoader类
以下内容纯属个人见解,如有不足之处,期待纠正。ServiceLoader源码来自jdk11
在Java的编程世界中,有时我们需要为某个接口提供多种实现,并且在运行时动态地加载这些实现。为了实现这种解耦与插件化的设计,Java提供了java.util.ServiceLoader
类。本文将深入探讨ServiceLoader
的用途、运行原理与实现原理,并通过一个实战Demo来加深对它的理解。
一、用途与使用场景
ServiceLoader
的主要用途是加载和实例化服务提供者,这里的“服务”通常指的是实现了某个接口的类。使用场景包括但不限于插件式架构、框架扩展、第三方库集成等。在这些场景中,核心代码定义了服务接口,而服务提供者(第三方或用户自定义)则实现了这些接口。通过ServiceLoader
,核心代码可以在运行时动态地加载和使用这些服务提供者,而无需在编译时知道它们的存在。
使用场景包括但不限于:
- 插件式架构:当需要支持多种插件时,可以使用
ServiceLoader
动态加载这些插件。 - 框架扩展:框架开发者可以定义一些接口,允许使用者通过实现这些接口来扩展框架的功能。
- 第三方库集成:当需要集成多个第三方库时,可以使用
ServiceLoader
来加载这些库的实现类。
二、运行原理与实现原理
ServiceLoader
的运行原理基于Java的SPI(Service Provider Interface)机制。SPI允许第三方为某个接口提供实现,并将这些实现类配置在特定的目录下。ServiceLoader
通过扫描这些目录,找到并加载实现类,然后实例化它们。
具体实现原理如下:
- 查找配置文件:
ServiceLoader
在类路径(classpath)下的META-INF/services
目录中查找以接口全限定名命名的文件。例如,如果接口名为com.example.MyService
,则ServiceLoader
会查找名为com.example.MyService
的文件。 - 解析配置文件:找到文件后,
ServiceLoader
会读取文件内容。文件内容是一系列实现类的全限定名,每行一个。这些实现类就是服务提供者。 - 加载与实例化:对于每个在配置文件中找到的实现类名,
ServiceLoader
使用Java的类加载机制加载这些类,并创建它们的实例。这些实例被缓存在ServiceLoader
内部,供后续使用。 - 懒加载机制:
ServiceLoader
采用懒加载的方式,即只有在首次调用iterator.hasNext()
方法时才开始加载和实例化服务提供者。这种设计可以提高性能,尤其是在有大量服务提供者但只使用其中一部分的情况下。 - 缓存机制:加载过的服务提供者会被缓存起来,后续再次调用
iterator()
方法时,会返回与之前相同的实例。
三、源码解析
只截部分源码:
- 通过调用load进行参数设置,不会真正的加载类
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
private ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl) {
Objects.requireNonNull(svc);
if (VM.isBooted()) {
checkCaller(caller, svc);
if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
}
} else {
// if we get here then it means that ServiceLoader is being used
// before the VM initialization has completed. At this point then
// only code in the java.base should be executing.
Module callerModule = caller.getModule();
Module base = Object.class.getModule();
Module svcModule = svc.getModule();
if (callerModule != base || svcModule != base) {
fail(svc, "not accessible to " + callerModule + " during VM init");
}
// restricted to boot loader during startup
cl = null;
}
this.service = svc;
this.serviceName = svc.getName();
this.layer = null;
this.loader = cl;
this.acc = (System.getSecurityManager() != null)
? AccessController.getContext()
: null;
}
- 调用iterator后进行准备工作,最终实例化LazyClassPathLookupIterator,再调用iterator.hasNext()时,进行真正的加载类
public Iterator<S> iterator() {
// create lookup iterator if needed
if (lookupIterator1 == null) {
// lookme 这里开始加载类
lookupIterator1 = newLookupIterator();
}
return new Iterator<S>() {
// record reload count
final int expectedReloadCount = ServiceLoader.this.reloadCount;
// index into the cached providers list
int index;
/**
* Throws ConcurrentModificationException if the list of cached
* providers has been cleared by reload.
*/
private void checkReloadCount() {
if (ServiceLoader.this.reloadCount != expectedReloadCount)
throw new ConcurrentModificationException();
}
@Override
public boolean hasNext() {
checkReloadCount();
if (index < instantiatedProviders.size())
return true;
return lookupIterator1.hasNext();
}
@Override
public S next() {
checkReloadCount();
S next;
if (index < instantiatedProviders.size()) {
next = instantiatedProviders.get(index);
} else {
next = lookupIterator1.next().get();
instantiatedProviders.add(next);
}
index++;
return next;
}
};
}
// 主要看LazyClassPathLookupIterator,它实现了Iterator接口,当调用hasNext时,会触发加载
private Iterator<Provider<S>> newLookupIterator() {
assert layer == null || loader == null;
if (layer != null) {
return new LayerLookupIterator<>();
} else {
Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
return new Iterator<Provider<S>>() {
@Override
public boolean hasNext() {
return (first.hasNext() || second.hasNext());
}
@Override
public Provider<S> next() {
if (first.hasNext()) {
return first.next();
} else if (second.hasNext()) {
return second.next();
} else {
throw new NoSuchElementException();
}
}
};
}
}
@Override
public boolean hasNext() {
if (acc == null) {
// lookme
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private boolean hasNextService() {
while (nextProvider == null && nextError == null) {
try {
// lookme
Class<?> clazz = nextProviderClass();
if (clazz == null)
return false;
if (clazz.getModule().isNamed()) {
// ignore class if in named module
continue;
}
if (service.isAssignableFrom(clazz)) {
Class<? extends S> type = (Class<? extends S>) clazz;
Constructor<? extends S> ctor
= (Constructor<? extends S>)getConstructor(clazz);
ProviderImpl<S> p = new ProviderImpl<S>(service, type, ctor, acc);
nextProvider = (ProviderImpl<T>) p;
} else {
fail(service, clazz.getName() + " not a subtype");
}
} catch (ServiceConfigurationError e) {
nextError = e;
}
}
return true;
}
private Class<?> nextProviderClass() {
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null) {
configs = ClassLoader.getSystemResources(fullName);
} else if (loader == ClassLoaders.platformClassLoader()) {
// The platform classloader doesn't have a class path,
// but the boot loader might.
if (BootLoader.hasClassPath()) {
configs = BootLoader.findResources(fullName);
} else {
configs = Collections.emptyEnumeration();
}
} else {
configs = loader.getResources(fullName);
}
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return null;
}
pending = parse(configs.nextElement());
}
String cn = pending.next();
try {
return Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service, "Provider " + cn + " not found");
return null;
}
}
四、实战Demo
下面是一个简单的ServiceLoader
使用示例,演示了如何定义一个服务接口、实现该接口、配置服务提供者,以及使用ServiceLoader
加载和使用这些服务提供者。
项目结构:
|____src
| |____main
| | |____java
| | | |____com
| | | | |____xuance
| | | | | |____serviceloader
| | | | | | |____OneXcServiceImpl.java
| | | | | | |____ServiceLoaderDemo.java
| | | | | | |____TwoXcServiceImpl.java
| | | | | | |____XcService.java
| | |____resources
| | | |____META-INF
| | | | |____services
| | | | | |____com.xuance.serviceloader.XcService
首先,定义服务接口XcService
:
package com.xuance.serviceloader;
public interface XcService {
void execute();
}
然后,创建两个服务提供者实现类OneXcServiceImpl、TwoXcServiceImpl
:
package com.xuance.serviceloader;
public class OneXcServiceImpl implements XcService {
@Override
public void execute() {
System.out.println("This is OneXcServiceImpl...");
}
}
package com.xuance.serviceloader;
public class TwoXcServiceImpl implements XcService{
@Override
public void execute() {
System.out.println("This is TwoXcServiceImpl...");
}
}
接下来,在META-INF/services
目录下创建名为com.xuance.serviceloader.XcService
的文件,并添加实现类的全限定名:
com.xuance.serviceloader.OneXcServiceImpl
com.xuance.serviceloader.TwoXcServiceImpl
最后,使用ServiceLoader
加载和使用服务提供者:
package com.xuance.serviceloader;
import java.util.ServiceLoader;
public class ServiceLoaderDemo {
public static void main(String[] args) {
// 加载MyService接口的实现类
ServiceLoader<XcService> serviceLoader = ServiceLoader.load(XcService.class);
// 遍历并调用每个服务提供者的execute方法
Iterator<XcService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
iterator.next().execute();
}
}
}
运行上述代码,你会看到如下输出:
This is OneXcServiceImpl...
This is TwoXcServiceImpl...
这表明ServiceLoader
成功加载了OneXcServiceImpl
和TwoXcServiceImpl
类,并调用了它的execute
方法。
它提供了一种灵活且强大的机制,使得在Java中实现插件式架构和动态扩展变得轻而易举。在实际开发中,我们可以利用ServiceLoader
来解耦核心代码与具体实现,从而实现更加灵活和可扩展的应用程序。