在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;
}
}
Comments NOTHING