Java SPI机制:ServiceLoader实现原理及应用剖析
一、背景
SPI,全称Service Provider Interfaces
,服务提供接口。是Java提供的一套供第三方实现或扩展使用的技术体系。主要通过解耦服务具体实现以及服务使用,使得程序的可扩展性大大增强,甚至可插拔。
基于服务的注册与发现机制,服务提供者向系统注册服务,服务使用者通过查找发现服务,可以达到服务的提供与使用的分离,甚至完成对服务的管理。
JDK中,基于SPI的思想,提供了默认具体的实现,ServiceLoader
。利用JDK自带的ServiceLoader
,可以轻松实现面向服务的注册与发现
,完成服务提供与使用的解耦
。
完成分离后的服务,使得服务提供方的修改或替换,不会给服务使用方带来代码上的修改,基于面向接口的服务约定,提供方和使用方各自直接面向接口编程,而不用关注对方的具体实现。同时,服务使用方使用到服务时,也才会真正意义上去发现服务
,以完成服务的初始化
,形成了服务的动态加载
。
在Java或Android系统实现或项目实践领域,也有直接基于ServiceLoader
的功能实现,或基于ServiceLoader
实现基础上,对其进行的进一步扩展与优化使用。
二、ServiceLoader
实现原理
先看一下JDK中ServiceLoader
的具体实现。
ServiceLoader
位于java.util
包中,其主体部分,代码如下:
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;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(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();
}
// Private inner class implementing fully-lazy provider lookup
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
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;
}
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
}
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);
}
}
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);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
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();
}
};
}
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();
}
};
}
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
ClassLoader prev = null;
while (cl != null) {
prev = cl;
cl = cl.getParent();
}
return ServiceLoader.load(service, prev);
}
public String toString() {
return "java.util.ServiceLoader[" + service.getName() + "]";
}
复制代码
外部使用时,往往通过load(Class<S> service, ClassLoader loader)
或load(Class<S> service)
调用,最后都是在reload
方法中创建了LazyIterator
对象,LazyIterator
是ServiceLoader
的内部类,实现了Iterator
接口,其作用是一个懒加载的迭代器,在hasNextService
方法中,完成了对位于META-INF/services/
目录下的配置文件的解析,并在nextService
方法中,完成了对具体实现类的实例化。
META-INF/services/
,是ServiceLoader
中约定的接口与实现类的关系配置目录,文件名是接口全限定类名,内容是接口对应的具体实现类,如果有多个实现类,分别将不同的实现类都分别作为每一行去配置。解析过程中,通过LinkedHashMap<String,S>
数据结构的providers
,将已经发现了的接口实现类进行了缓存,并对外提供的iterator()
方法,方便外部遍历。
Android中使用的是OpenJDK,其ServiceLoader
的实现与Java JDK中稍有不同,但主体逻辑和实现过程都是一致的。
总体上,ServiceLoader
的一般实现与使用过程包含了服务接口约定
、服务实现
、服务注册
、服务发现与使用
这四个步骤。
如下是一个简单的Java项目中,ServiceLoader
使用示例。
项目结构:
--------------
|____src
| |____main
| | |____resources
| | | |____META-INF
| | | | |____services
| | | | | |____com.corn.javalib.IMyServiceProvider
| | |____java
| | | |____com
| | | | |____corn
| | | | | |____javalib
| | | | | | |____IMyServiceProvider.java
| | | | | | |____TestClass.java
| | | | | | |____MyServiceProviderImpl2.java
| | | | | | |____MyServiceProviderImpl1.java
复制代码
1,服务接口约定:
IMyServiceProvider
定义了服务的接口约定:
package com.corn.javalib;
public interface IMyServiceProvider {
String getName();
}
复制代码
2,服务实现:
MyServiceProviderImpl1
、MyServiceProviderImpl2
是具体的接口实现类:
package com.corn.javalib;
public class MyServiceProviderImpl1 implements IMyServiceProvider {
@Override
public String getName() {
return "name:ProviderImpl1";
}
}
复制代码
package com.corn.javalib;
public class MyServiceProviderImpl2 implements IMyServiceProvider {
@Override
public String getName() {
return "name:ProviderImpl2";
}
}
复制代码
3,服务注册(实际上向系统登记服务提供者与服务接口之间的映射关系,以便使用方的服务发现):
/META-INF/services/
目录下创建文件com.corn.javalib.IMyServiceProvider
,内容为:
com.corn.javalib.MyServiceProviderImpl1
com.corn.javalib.MyServiceProviderImpl2
复制代码
4,服务发现与使用:
TestClass.java
为服务使用方。
package com.corn.javalib;
import java.util.Iterator;
import java.util.ServiceLoader;
public class TestClass {
public static void main(String[] argus){
ServiceLoader<IMyServiceProvider> serviceLoader = ServiceLoader.load(IMyServiceProvider.class);
Iterator iterator = serviceLoader.iterator();
while (iterator.hasNext()){
IMyServiceProvider item = (IMyServiceProvider)iterator.next();
System.out.println(item.getName() + ": " + item.hashCode());
}
}
}
复制代码
输出结果为:
name:ProviderImpl1: 491044090
name:ProviderImpl2: 644117698
复制代码
三、ServiceLoader
使用实例
3.1 注解处理器的发现过程
使用到编译时注解时,定义注解并在目标元素上标注上注解后,都还需要定义一个具体的注解处理器。注解处理器的作用在于对注解的发现与处理,如实现自定义的注解处理逻辑,生成新的Java文件等。那注解处理器是如何在编译阶段被Javac编译器发现并调用的呢,这其中的过程实际上用到了ServiceLoader
机制。
Javac编译过程中,会执行注解处理器发现和调用流程,Javac本身也是用java编写的,同时去编译java源码文件的编译工具。为了方便阐述Javac中与注解处理器相关的逻辑,可以在自定义的注解处理器中故意抛出异常。以查看大概的执行路径。
Caused by: java.lang.NullPointerException
at com.corn.apt.AnnotationProcessor.process(AnnotationProcessor.java:42)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:794)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:705)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1800(JavacProcessingEnvironment.java:91)
at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1035)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1176)
at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1170)
at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:856)
at com.sun.tools.javac.main.Main.compile(Main.java:523)
复制代码
如上,是大概的执行流程。Open JDK中自带了Javac的源码,可以下载下来,主要的Javac源码部分位于包:com.sun.tools.javac
下。对照上述抛出的错误信息路径,梳理下具体的执行流程。
1,javac从com.sun.tools.javac.main.Main.compile
开始执行,其中调用了JavaCompiler
的compile
方法,compile
方法具体定义如下:
public void compile(List<JavaFileObject> sourceFileObject) throws Throwable {
compile(sourceFileObject, List.<String>nil(), null);
}
复制代码
其内部调用的compile方法,主体部分逻辑如下:
public void compile(List<JavaFileObject> sourceFileObjects,
List<String> classnames,
Iterable<? extends Processor> processors)
throws IOException // TODO: temp, from JavacProcessingEnvironment
{
if (processors != null && processors.iterator().hasNext())
explicitAnnotationProcessingRequested = true;
// as a JavaCompiler can only be used once, throw an exception if
// it has been used before.
if (hasBeenUsed)
throw new AssertionError("attempt to reuse JavaCompiler");
hasBeenUsed = true;
start_msec = now();
try {
initProcessAnnotations(processors);
// These method calls must be chained to avoid memory leaks
delegateCompiler =
processAnnotations(
enterTrees(stopIfError(CompileState.PARSE, parseFiles(sourceFileObjects))),
classnames);
delegateCompiler.compile2();
delegateCompiler.close();
elapsed_msec = delegateCompiler.elapsed_msec;
} catch (Abort ex) {
if (devVerbose)
ex.printStackTrace();
} finally {
if (procEnvImpl != null)
procEnvImpl.close();
}
}
复制代码
其中,主要调用了initProcessAnnotations(processors)
,和processAnnotations(...)
方法,且默认情况下, initProcessAnnotations
传入的processors
为null
,是对处理注解进行的初始化,其内部通过new JavacProcessingEnvironment(context, processors)
新建了JavacProcessingEnvironment
对象,JavacProcessingEnvironment
构造器中,通过调用initProcessorIterator(context, processors)
开始发现注解处理器。
我们重点看一下initProcessorIterator
具体过程。
private void initProcessorIterator(Context context, Iterable<? extends Processor> processors) {
Log log = Log.instance(context);
Iterator<? extends Processor> processorIterator;
if (options.get("-Xprint") != null) {
try {
Processor processor = PrintingProcessor.class.newInstance();
processorIterator = List.of(processor).iterator();
} catch (Throwable t) {
AssertionError assertError =
new AssertionError("Problem instantiating PrintingProcessor.");
assertError.initCause(t);
throw assertError;
}
} else if (processors != null) {
processorIterator = processors.iterator();
} else {
String processorNames = options.get("-processor");
JavaFileManager fileManager = context.get(JavaFileManager.class);
try {
// If processorpath is not explicitly set, use the classpath.
processorClassLoader = fileManager.hasLocation(ANNOTATION_PROCESSOR_PATH)
? fileManager.getClassLoader(ANNOTATION_PROCESSOR_PATH)
: fileManager.getClassLoader(CLASS_PATH);
/*
* If the "-processor" option is used, search the appropriate
* path for the named class. Otherwise, use a service
* provider mechanism to create the processor iterator.
*/
if (processorNames != null) {
processorIterator = new NameProcessIterator(processorNames, processorClassLoader, log);
} else {
processorIterator = new ServiceIterator(processorClassLoader, log);
}
} catch (SecurityException e) {
/*
* A security exception will occur if we can't create a classloader.
* Ignore the exception if, with hindsight, we didn't need it anyway
* (i.e. no processor was specified either explicitly, or implicitly,
* in service configuration file.) Otherwise, we cannot continue.
*/
processorIterator = handleServiceLoaderUnavailability("proc.cant.create.loader", e);
}
}
discoveredProcs = new DiscoveredProcessors(processorIterator);
}
复制代码
传入的参数processors为null,默认情况下,程序逻辑执行到else过程,来到processorIterator = new ServiceIterator(processorClassLoader, log)
。ServiceIterator
是一个内部类,重点看下其具体实现:
/**
* Use a service loader appropriate for the platform to provide an
* iterator over annotations processors. If
* java.util.ServiceLoader is present use it, otherwise, use
* sun.misc.Service, otherwise fail if a loader is needed.
*/
private class ServiceIterator implements Iterator<Processor> {
// The to-be-wrapped iterator.
private Iterator<?> iterator;
private Log log;
ServiceIterator(ClassLoader classLoader, Log log) {
Class<?> loaderClass;
String loadMethodName;
boolean jusl;
this.log = log;
try {
try {
loaderClass = Class.forName("java.util.ServiceLoader");
loadMethodName = "load";
jusl = true;
} catch (ClassNotFoundException cnfe) {
try {
loaderClass = Class.forName("sun.misc.Service");
loadMethodName = "providers";
jusl = false;
} catch (ClassNotFoundException cnfe2) {
// Fail softly if a loader is not actually needed.
this.iterator = handleServiceLoaderUnavailability("proc.no.service",
null);
return;
}
}
// java.util.ServiceLoader.load or sun.misc.Service.providers
Method loadMethod = loaderClass.getMethod(loadMethodName,
Class.class,
ClassLoader.class);
Object result = loadMethod.invoke(null,
Processor.class,
classLoader);
// For java.util.ServiceLoader, we have to call another
// method to get the iterator.
if (jusl) {
Method m = loaderClass.getMethod("iterator");
result = m.invoke(result); // serviceLoader.iterator();
}
// The result should now be an iterator.
this.iterator = (Iterator<?>) result;
} catch (Throwable t) {
log.error("proc.service.problem");
throw new Abort(t);
}
}
public boolean hasNext() {
try {
return iterator.hasNext();
} catch (Throwable t) {
if ("ServiceConfigurationError".
equals(t.getClass().getSimpleName())) {
log.error("proc.bad.config.file", t.getLocalizedMessage());
}
throw new Abort(t);
}
}
public Processor next() {
try {
return (Processor)(iterator.next());
} catch (Throwable t) {
if ("ServiceConfigurationError".
equals(t.getClass().getSimpleName())) {
log.error("proc.bad.config.file", t.getLocalizedMessage());
} else {
log.error("proc.processor.constructor.error", t.getLocalizedMessage());
}
throw new Abort(t);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
复制代码
终于,我们发现,ServiceIterator
提供了适合于平台的服务发现机制,发现注解处理器。其中,优先通过反射的方式,去动态调用了java.util.ServiceLoader
对应的load
方法,具体是通过语句Object result = loadMethod.invoke(null, Processor.class, classLoader)
实现,其中,传入了接口参数Processor.class
以此完成了基于ServiceLoader
的服务动态发现过程。
查找到注解处理器后,后续主要就是通过调用对应注解处理器中的方法,如自定义的注解处理器中常常重写的init
和process
方法等,以此实现注解处理器所需要完成的针对注解这块的逻辑处理。
至此,编译时的注解处理器的服务发现,实际上是通过ServiceLoader
去实现的,流程上已经相对清晰。
对应的,我们也知道,在定义完具体的注解处理器后,需要我们在对应的/META-INF/services/
中去注册Processor
接口与具体的注解处理器实现类之间的关系。当然,这一步操作也可以听过Google AutoService
去完成。
3.2 基于ServiceLoader
实现不同组件间的通信与解耦
Android项目组件化过程中,不具有依赖关系的组件或模块间,常常涉及到组件间服务的提供与使用,如A模块需要调用B模块的方法等。基于ServiceLoader机制,实际上已经为我们提供了一种Android组件化之间的组件解耦与通信机制。通过将接口约定下沉到公共baseLib模块,不同的模块内可以按照实际需要,提供接口的具体实现,其他模块直接通过形如ServiceLoader.load(IMyServiceProvider.class)
方式,即可得到具体的服务并调用之。
下面是一个简单的Android组件化后的项目工程结构示例:
.libBase
|____build.gradle
|____src
| |____main
| | |____res
| | | |____drawable
| | | |____values
| | | | |____strings.xml
| | |____AndroidManifest.xml
| | |____java
| | | |____com
| | | | |____corn
| | | | | |____libbase
| | | | | | |____IMyServiceProvider.java
.LibA
|____libs
|____build.gradle
|____src
| |____main
| | |____res
| | | |____drawable
| | | |____values
| | | | |____strings.xml
| | |____resources
| | | |____META-INF
| | | | |____services
| | | | | |____com.corn.libbase.IMyServiceProvider
| | |____AndroidManifest.xml
| | |____java
| | | |____com
| | | | |____corn
| | | | | |____liba
| | | | | | |____MyServiceProviderImpl2.java
| | | | | | |____MyServiceProviderImpl1.java
.LibB
|____build.gradle
|____src
| |____main
| | |____res
| | | |____drawable
| | | |____values
| | | | |____strings.xml
| | |____resources
| | | |____META-INF
| | | | |____services
| | | | | |____com.corn.libbase.IMyServiceProvider
| | |____AndroidManifest.xml
| | |____java
| | | |____com
| | | | |____corn
| | | | | |____libb
| | | | | | |____MyServiceProviderImpl3.java
复制代码
通过将服务接口约定定义在libBase模块,具体的服务实现提供可以在LibA或LibB等上层模块,然后分别在META-INF/services/
中注册对应的接口与服务实现之间的关系,使得打包后合并对应的映射关系。
项目中其他模块,也可以直接通过形如如下方式去调用具体的服务方法。
ServiceLoader<IMyServiceProvider> serviceLoader = ServiceLoader.load(IMyServiceProvider.class);
Iterator iterator = serviceLoader.iterator();
while (iterator.hasNext()){
IMyServiceProvider item = (IMyServiceProvider)iterator.next();
Log.d(TAG, item.getName() + ": " + item.hashCode());
}
复制代码
如果是在release环境下,还需要针对接口和实现类配置反混淆。否则一旦混淆后,基于接口名或接口实现的配置文件中将不能找到对应的目标类。
以此,通过基于系统ServiceLoader
的方式,通过面向接口的编程方式,实现了组件间的服务解耦。
3.3 美团WMRouter中对ServiceLoader
的改进与使用
我们发现,ServiceLoder
每次load
过程,实际上都重走了整个的ServiceLoder
过程,因此,如果直接采用ServiceLoder
,每次都需要对具体实现类都重走了查找和通过反射去实例化等过程,且针对同一接口,可能有多个不同的服务实现。
在借鉴系统ServiceLoader
思想和实现过程的基础上,美团WMRouter中,对ServiceLoader
进行了改进,主要改进点如下:
1,将系统ServiceLoader
中的服务注册从系统原来的/META-INF/services/
中定义改成了WMRouter中封装好的ServiceLoader
中的静态Map<Class, ServiceLoader> SERVICES
属性和ServiceLoader
实例的HashMap<String, ServiceImpl> mMap
中。
SERVICES
是一个静态变量,存储的是接口与对应的ServiceLoader
关系映射,
mMap
作为ServiceLoader
的内部属性,存储的是对应ServiceLoader
实例中每个接口实现类的key(每个key表示每个不同的接口实现)和对应的实现类的关系映射。
2,可以通过上述的每个接口的key,让使用方去具体调用接口的某个具体实现服务。
3,接口实现类,通过反射创建的对象,可以决定是否存置于SingletonPool
单例池中,以方便接口实现类的下次使用,相当于做了一次对象的缓存。
下面具体看下WMRouter中关于ServiceLoader
改造部分的源码实现。
ServiceLoader实现:
package com.sankuai.waimai.router.service;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.sankuai.waimai.router.annotation.RouterProvider;
import com.sankuai.waimai.router.components.RouterComponents;
import com.sankuai.waimai.router.core.Debugger;
import com.sankuai.waimai.router.interfaces.Const;
import com.sankuai.waimai.router.utils.LazyInitHelper;
import com.sankuai.waimai.router.utils.SingletonPool;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 通过接口Class获取实现类
*
* @param <I> 接口类型
*/
public class ServiceLoader<I> {
private static final Map<Class, ServiceLoader> SERVICES = new HashMap<>();
private static final LazyInitHelper sInitHelper = new LazyInitHelper("ServiceLoader") {
@Override
protected void doInit() {
try {
// 反射调用Init类,避免引用的类过多,导致main dex capacity exceeded问题
Class.forName(Const.SERVICE_LOADER_INIT)
.getMethod(Const.INIT_METHOD)
.invoke(null);
Debugger.i("[ServiceLoader] init class invoked");
} catch (Exception e) {
Debugger.fatal(e);
}
}
};
/**
* @see LazyInitHelper#lazyInit()
*/
public static void lazyInit() {
sInitHelper.lazyInit();
}
/**
* 提供给InitClass使用的初始化接口
*
* @param interfaceClass 接口类
* @param implementClass 实现类
*/
public static void put(Class interfaceClass, String key, Class implementClass, boolean singleton) {
ServiceLoader loader = SERVICES.get(interfaceClass);
if (loader == null) {
loader = new ServiceLoader(interfaceClass);
SERVICES.put(interfaceClass, loader);
}
loader.putImpl(key, implementClass, singleton);
}
/**
* 根据接口获取 {@link ServiceLoader}
*/
@SuppressWarnings("unchecked")
public static <T> ServiceLoader<T> load(Class<T> interfaceClass) {
sInitHelper.ensureInit();
if (interfaceClass == null) {
Debugger.fatal(new NullPointerException("ServiceLoader.load的class参数不应为空"));
return EmptyServiceLoader.INSTANCE;
}
ServiceLoader service = SERVICES.get(interfaceClass);
if (service == null) {
synchronized (SERVICES) {
service = SERVICES.get(interfaceClass);
if (service == null) {
service = new ServiceLoader(interfaceClass);
SERVICES.put(interfaceClass, service);
}
}
}
return service;
}
/**
* key --> class name
*/
private HashMap<String, ServiceImpl> mMap = new HashMap<>();
private final String mInterfaceName;
private ServiceLoader(Class interfaceClass) {
if (interfaceClass == null) {
mInterfaceName = "";
} else {
mInterfaceName = interfaceClass.getName();
}
}
private void putImpl(String key, Class implementClass, boolean singleton) {
if (key != null && implementClass != null) {
mMap.put(key, new ServiceImpl(key, implementClass, singleton));
}
}
/**
* 创建指定key的实现类实例,使用 {@link RouterProvider} 方法或无参数构造。对于声明了singleton的实现类,不会重复创建实例。
*
* @return 可能返回null
*/
public <T extends I> T get(String key) {
return createInstance(mMap.get(key), null);
}
/**
* 创建指定key的实现类实例,使用Context参数构造。对于声明了singleton的实现类,不会重复创建实例。
*
* @return 可能返回null
*/
public <T extends I> T get(String key, Context context) {
return createInstance(mMap.get(key), new ContextFactory(context));
}
/**
* 创建指定key的实现类实例,使用指定的Factory构造。对于声明了singleton的实现类,不会重复创建实例。
*
* @return 可能返回null
*/
public <T extends I> T get(String key, IFactory factory) {
return createInstance(mMap.get(key), factory);
}
/**
* 创建所有实现类的实例,使用 {@link RouterProvider} 方法或无参数构造。对于声明了singleton的实现类,不会重复创建实例。
*
* @return 可能返回EmptyList,List中的元素不为空
*/
@NonNull
public <T extends I> List<T> getAll() {
return getAll((IFactory) null);
}
/**
* 创建所有实现类的实例,使用Context参数构造。对于声明了singleton的实现类,不会重复创建实例。
*
* @return 可能返回EmptyList,List中的元素不为空
*/
@NonNull
public <T extends I> List<T> getAll(Context context) {
return getAll(new ContextFactory(context));
}
/**
* 创建所有实现类的实例,使用指定Factory构造。对于声明了singleton的实现类,不会重复创建实例。
*
* @return 可能返回EmptyList,List中的元素不为空
*/
@NonNull
public <T extends I> List<T> getAll(IFactory factory) {
Collection<ServiceImpl> services = mMap.values();
if (services.isEmpty()) {
return Collections.emptyList();
}
List<T> list = new ArrayList<>(services.size());
for (ServiceImpl impl : services) {
T instance = createInstance(impl, factory);
if (instance != null) {
list.add(instance);
}
}
return list;
}
/**
* 获取指定key的实现类。注意,对于声明了singleton的实现类,获取Class后还是可以创建新的实例。
*
* @return 可能返回null
*/
@SuppressWarnings("unchecked")
public <T extends I> Class<T> getClass(String key) {
return (Class<T>) mMap.get(key).getImplementationClazz();
}
/**
* 获取所有实现类的Class。注意,对于声明了singleton的实现类,获取Class后还是可以创建新的实例。
*
* @return 可能返回EmptyList,List中的元素不为空
*/
@SuppressWarnings("unchecked")
@NonNull
public <T extends I> List<Class<T>> getAllClasses() {
List<Class<T>> list = new ArrayList<>(mMap.size());
for (ServiceImpl impl : mMap.values()) {
Class<T> clazz = (Class<T>) impl.getImplementationClazz();
if (clazz != null) {
list.add(clazz);
}
}
return list;
}
@SuppressWarnings("unchecked")
@Nullable
private <T extends I> T createInstance(@Nullable ServiceImpl impl, @Nullable IFactory factory) {
if (impl == null) {
return null;
}
Class<T> clazz = (Class<T>) impl.getImplementationClazz();
if (impl.isSingleton()) {
try {
return SingletonPool.get(clazz, factory);
} catch (Exception e) {
Debugger.fatal(e);
}
} else {
try {
if (factory == null) {
factory = RouterComponents.getDefaultFactory();
}
T t = factory.create(clazz);
Debugger.i("[ServiceLoader] create instance: %s, result = %s", clazz, t);
return t;
} catch (Exception e) {
Debugger.fatal(e);
}
}
return null;
}
@Override
public String toString() {
return "ServiceLoader (" + mInterfaceName + ")";
}
public static class EmptyServiceLoader extends ServiceLoader {
public static final ServiceLoader INSTANCE = new EmptyServiceLoader();
public EmptyServiceLoader() {
super(null);
}
@NonNull
@Override
public List<Class> getAllClasses() {
return Collections.emptyList();
}
@NonNull
@Override
public List getAll() {
return Collections.emptyList();
}
@NonNull
@Override
public List getAll(IFactory factory) {
return Collections.emptyList();
}
@Override
public String toString() {
return "EmptyServiceLoader";
}
}
}
复制代码
首先通过对外提供了doInit
方法,让系统通过反射的方式调用ServiceLoaderInit
类的init
方法,通过调用ServiceLoader.put
方法,将接口、接口实现类的key和接口实现类,依次装载进SERVICES
和mMap
中。以此完成了映射关系的注册。通过Router
类,进一步封装好对ServiceLoader
的调用,以方便外部适用方更方便的去使用,最终通过如Router.getService(ILocationService.class, "keyValue")
等方式去调用。
WMRouterServiceLoader
本身,其他的服务查找,服务具体实现类的初始化等相对都比较简单,下面重点看下服务实现类的实例缓存逻辑。
@Nullable
private <T extends I> T createInstance(@Nullable ServiceImpl impl, @Nullable IFactory factory) {
if (impl == null) {
return null;
}
Class<T> clazz = (Class<T>) impl.getImplementationClazz();
if (impl.isSingleton()) {
try {
return SingletonPool.get(clazz, factory);
} catch (Exception e) {
Debugger.fatal(e);
}
} else {
try {
if (factory == null) {
factory = RouterComponents.getDefaultFactory();
}
T t = factory.create(clazz);
Debugger.i("[ServiceLoader] create instance: %s, result = %s", clazz, t);
return t;
} catch (Exception e) {
Debugger.fatal(e);
}
}
return null;
}
复制代码
createInstance
方法中,通过判断impl.isSingleton()
,来决定是否从SingletonPool
中读取缓存的对象实例。SingletonPool
是一个单例缓存类,其中通过静态的CACHE
常量,缓存了对应的类class
与已经实例化过的对象之间的映射关系,下次再次需要读取时,直接判断CACHE
中是否已经存在单例对象,有则直接取出,否则创建并存入。
/**
* 单例缓存
*
*/
public class SingletonPool {
private static final Map<Class, Object> CACHE = new HashMap<>();
@SuppressWarnings("unchecked")
public static <I, T extends I> T get(Class<I> clazz, IFactory factory) throws Exception {
if (clazz == null) {
return null;
}
if (factory == null) {
factory = RouterComponents.getDefaultFactory();
}
Object instance = getInstance(clazz, factory);
Debugger.i("[SingletonPool] get instance of class = %s, result = %s", clazz, instance);
return (T) instance;
}
@NonNull
private static Object getInstance(@NonNull Class clazz, @NonNull IFactory factory) throws Exception {
Object t = CACHE.get(clazz);
if (t != null) {
return t;
} else {
synchronized (CACHE) {
t = CACHE.get(clazz);
if (t == null) {
Debugger.i("[SingletonPool] >>> create instance: %s", clazz);
t = factory.create(clazz);
//noinspection ConstantConditions
if (t != null) {
CACHE.put(clazz, t);
}
}
}
return t;
}
}
}
复制代码
SingletonPool
的存在,避免了ServiceLoader
可能需要的重复实例化过程,但同时,带来的问题是服务对象的生命周期的延长化,会导致长期占据内存。由此,作为框架提供方,特意在服务具体实现类的注解上,加上了一个singleton
参数供使用方去决定此服务实现类是否采用单例形式,以决定是否需要缓存。
也就是常见的具体服务类的实现上,注解写法形式如下:
@RouterService(interfaces = IAccountService.class, key = DemoConstant.SINGLETON, singleton = true)
public class FakeAccountService implements IAccountService {
...
}
复制代码
至此,WMRouter中对ServiceLoader
的改进部分分析完成。
四、结语
基于服务提供与发现的思想,系统自带的ServiceLoader
以及基于此思想基础上的演化形式,被广泛的使用到实际的项目中。本质上,通过服务接口约定、服务注册与服务发现,完成将服务提供方与服务使用方的解耦,大大扩展了系统的可扩展性。服务注册的本质,是将服务接口与具体服务实现的映射关系注册到系统或特定实现中。服务发现的过程,本质上是向系统或特定实现去匹配对应的具体实现类,但在写法上是基于接口的编程方式,因为服务使用方和服务提供方彼此都是透明与未感知的。基于SPI
思想的ServiceLoader
实现及演化,在项目的组件化,或实现扩展性功能,甚至完成具有可插拔能力的插件化模块时,往往都被广泛使用到。
作者:HappyCorn
链接:https://juejin.im/post/5d2db85d6fb9a07ea7134408
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Java SPI机制:ServiceLoader实现原理及应用剖析的更多相关文章
- JDK源码解析之Java SPI机制
1. spi 是什么 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件. 系统设计的各个抽象,往往 ...
- 聊聊Java SPI机制
一.Java SPI机制 SPI(Service Provider Interface)是JDK内置的服务发现机制,用在不同模块间通过接口调用服务,避免对具体服务服务接口具体实现类的耦合.比如JDBC ...
- 你应该了解的 Java SPI 机制
前言 不知大家现在有没有去公司复工,我已经在家办公将近 3 周了,同时也在家呆了一个多月:还好工作并没有受到任何影响,我个人一直觉得远程工作和 IT 行业是非常契合的,这段时间的工作效率甚至比在办公室 ...
- Java spi机制浅谈
最近看到公司的一些框架和之前看到的开源的一些框架的一些服务发现和接入都采用了java的spi机制. 所以简单的总结下java spi机制的思想. 我们系统里抽象的各个模块,往往有很多不同的实现方案,比 ...
- Java SPI机制实战详解及源码分析
背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...
- 组件化框架设计之Java SPI机制(三)
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本篇文章将从深入理解java SPI机制来介绍组件化框架设计: ...
- Java SPI 机制实现解耦与本地化
SPI 是 Java 提供的一种服务加载方式,全名为 Service Provider Interface,可以避免在 Java 代码中写死服务的提供者,而是通过 SPI 服务加载机制进行服务的注册和 ...
- Java SPI机制详解
Java SPI机制详解 1.什么是SPI? SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.SPI是一种动态替换发现的机制, 比如有个 ...
- java SPI机制
1. SPI是Service Provider Interfaces的简称.根据Java的SPI规范,我们可以定义一个服务接口,具体的实现由对应的实现者去提供,即Service Provider(服务 ...
随机推荐
- Ajax异步后台加载Html绑定不上事件
因项目需要,需要实时从后台动态加载html,开发过程中,遇到事件绑定不上,后来百度一番,大概意思:ajax是异步加载的,页面一开始绑定事件的时候,后台数据还没有传过来,就绑定事件,这个时候找不到这个d ...
- JavaScript BOM Cookie 的用法
JavaScript Cookie Cookie是计算机上存储在小文本文件中的数据.当Web服务器将网页发送到浏览器时,连接将关闭,服务器将忘记用户的所有内容.发明Cookie是为了解决“如何记住用户 ...
- HeadFirst设计模式---观察者
表达公式 注册者 + 订阅者 = 观察者模式 设计气象站 气象站接口 /** ** 布告板 ** @author lollipop ** @since 2019/11/24 **/ public in ...
- 团队——Alpha2版本发布
这个作业属于哪个课程 课程链接 这个作业要求在哪里 作业要求的链接 团队名称 杨荣模杰和他的佶祥虎 这个作业的目标 发布并说明产品Alpha2版本 一.团队成员的学号姓名列表 学号 姓名 201731 ...
- java+selenium3学习
http://blog.csdn.net/u011541946/article/details/72898514 http://git.oschina.net/zhengshuheng https:/ ...
- 深度学习的encoder和decoder
所谓编码,就是将输入序列转化成一个固定长度的向量:解码,就是将之前生成的固定向量再转化成输出序列.
- pyenv python 多版本管理工具
pyenv fork 自rbenv 以及ruby-build ,然后修改为转为python 使用 venv 以及virtualenv 解决了版本选择的问题,pyenv 同时为我们解决 了python ...
- 4.Vue双向绑定
1.什么是双向数据绑定 Vue.js 是一个 MVVM 框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化.这也算是 Vue.js 的精髓之处了 ...
- [LeetCode] 44. Wildcard Matching 外卡匹配
Given an input string (s) and a pattern (p), implement wildcard pattern matching with support for '? ...
- WIMBuilder2软件包及精简方案,请把补丁包放到指定位置
WIMBuilder2软件包及精简方案请把补丁包放到指定位置WimBuilder2-20190901\Projects\WIN10XPE\目录下面精简方案测试适用于LTSB2019.17763.316 ...