关于java的SPI机制,可以参考: https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html
为了实现在模块装配时的时候不在程序中动态指明,需要提供一种服务发现机制,为某个接口寻找服务实现的机制,就是将装配的控制权转移到程序之外,在模块化设计中这个机制尤其重要。
Java SPI(Service Provider Interface)的具体约定如下:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/ 目录中同时创建一个以服务接口命名的文件,该文件中的内容就是实现该服务接口的具体实现类。
该实例用于演示在maven环境下的service声明定义,居然在idea下无法调试启动...
resources下指定目录 META-INF/services目录(被写死在代码中):
public final class ServiceLoader
注意需要将服务声明的文件名称定义为: example.spi.service.Service,与接口名称一致,其中的内容包括:
example.spi.service.PrintServiceImpl
example.spi.service.EchoServiceImpl
不同的实现类需要以换行符分隔,在SpiMain的主方法中,使用ServiceLoader进行加载操作:
public static void main(String[] args) {
ServiceLoader serviceLoader = ServiceLoader.load(Service.class);
for (Service service : serviceLoader) {
service.printInfo();
}
}
在ServiceLoader的load方法中,会初始化ServiceLoader.LazyIterator,实现了标准的迭代器接口Iterator(以及hasNext, next方法),其中hasNextService()方法中会从当前的ClassLoader加载 PREFIX("META-INF/services/") + service名称(class名称),
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;
}
在parse方法中会根据文件进行逐行解析,如果下一步存在对应的实现,该方法返回true,接着就可以访问nextService方法来获得下一个服务实现:
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
}
将nextName(下一行实现的类名称)进行实例化,注意需要实现对应接口并有无参构造函数,并将实现放到providers这个Map中,以便下次直接使用(除非进行reload操作,否则不会更新该表,而reload操作是在ServiceLoader启动时初始化的)。
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
虽然ServiceLoader也算是使用了延迟加载,但只能通过遍历所有获取,将接口的实现类全部加载并实例化一遍,而且只能通过Iterator形式获取,不能根据某个参数来获取。
参考: https://my.oschina.net/pingpangkuangmo/blog/508963 来分析dubbo的服务体系。
在dubbo中大量使用了该方式进行服务发现和服务注册,并进行了一定的扩展,实现了 com.alibaba.dubbo.common.extension.ExtensionLoader 类,内部注册服务的目录迁移为 META-INF/dubbo/internal类型:
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
在ExtensionLoader中,不仅会对其中的服务进行初始化,还可以像IOC一样,对引用的其他服务进行set操作,这部分参考ExtensionLoader.injectExtension(T instance)类,进行注入的类型必须为set开头方法,参数只有一个,方法为public,而且参数必须为接口(只有接口ExtensionFactory才能根据该接口进行查找服务实现类)。
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
如果接口的实现由多个,则此时采取的策略是,并不去注入一个具体的实现者,而是注入一个动态生成的实现者,这个动态生成的实现者的逻辑是确定的,能够根据不同的参数来使用不同的实现者实现相应的方法。这个动态生成的实现者的class就是ExtensionLoader的Class> cachedAdaptiveClass
在查找SPI Annotation,使用dubbo配置的扩展方式进行注册,例如在获取AdaptiveExtensionFactory时,使用的构造函数用于加载扩展点:
public AdaptiveExtensionFactory() {
ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List list = new ArrayList();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
而getExtension()方法能够查找具体的objectFactory(SPI,Spring)可以从对应的文件声明以及spring容器中查找具体的bean。
对于ExtensionFactory会从三个地方加载extensionClass:
private Map> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if(defaultAnnotation != null) {
String value = defaultAnnotation.value();
if(value != null && (value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if(names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if(names.length == 1) cachedDefaultName = names[0];
}
}
Map> extensionClasses = new HashMap>();
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadFile(extensionClasses, DUBBO_DIRECTORY);
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}
分别为 /META-INF/dubbo/internal, /META-INF/dubbo/,META-INF/services。
可以看出dubbo的扩展机制虽然与SPI比较类似,但额外增加了其他功能,例如可以根据接口名称来获取服务,服务声明文件支持A=B的方式,此时A为名称B为实现类;支持扩展IOC依赖注入功能,可以为Service之间的依赖关系注入相关的服务并保证单例。