什么是SPI
SPI全称为Service Provider Interface
,是一种动态替换发现的机制,一种解耦非常优秀的思想,SPI可以很灵活的让接口和实现分离,让api提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展,在框架中比较常见,提高框架的可扩展性。
简单来说SPI是一种非常优秀的设计思想,它的核心就是解耦、方便扩展。
Java SPI机制–ServiceLoader
ServiceLoader
是Java提供的一种简单的SPI机制的实现,Java的SPI实现约定了以下两件事:
- 文件必须放在
META-INF/services/
目录底下 - 文件名必须为接口的全限定名,内容为接口实现的全限定名
这样就能够通过ServiceLoader
加载到文件中接口的实现。
demo展示
接口类
public interface LoadBalance {
}
实现类,这里引用了google
的auto-service
,可以在编译的时候生成配置文件com.charles.spi.LoadBalance
,内容是com.charles.spi.RandomLoadBalance
。
import com.google.auto.service.AutoService;
@AutoService(LoadBalance.class)
public class RandomLoadBalance implements LoadBalance {
public RandomLoadBalance() {
System.out.println("random init ...");
}
}
测试效果
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<LoadBalance> serviceLoader = ServiceLoader.load(LoadBalance.class);
Iterator<LoadBalance> iterator = serviceLoader.iterator();
while (iterator.hasNext()){
LoadBalance next = iterator.next();
System.out.println("获取到负载均衡策略:" + next);
}
}
}
源码解析
ServiceLoader#load()
,初始化了LazyIterator
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
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();
}
ServiceLoader.LazyIterator#next
,迭代器获取,根据接口名称获取配置文件,解析每一行数据,使用自定义的类加载器加载。
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);
}
}
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
}
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 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();
}
优缺点
- 浪费资源,会对每一个实现类进行实例化
- 无法区分具体的实现,做不到按需加载
Spring SPI机制–SpringFactoriesLoader
pring的SPI机制的约定如下:
- 配置文件必须在
META-INF/
目录下,文件名必须为spring.factories - 文件内容为键值对,一个键可以有多个值,只需要用逗号分割就行,同时键值都需要是类的全限定名,键和值可以没有任何类与类之间的关系,当然也可以有实现的关系。
demo展示
在META-INF/
目录下创建spring.factories
文件,LoadBalance
为键,RandomLoadBalance
为值。
com.charles.spi.LoadBalance=com.charles.spi.RandomLoadBalance
测试效果
public class SpringSpiTest {
public static void main(String[] args) {
List<LoadBalance> loadBalances = SpringFactoriesLoader.loadFactories(LoadBalance.class, null);
// List<String> factoryNames = SpringFactoriesLoader.loadFactoryNames(LoadBalance.class, null);
System.out.println(loadBalances);
}
}
源码解析
SpringFactoriesLoader#loadFactories
,获取factoryType
的类名。加载META-INF/spring.factories
目录下的文件,用Properties
来接收数据,用map做缓存,避免重复加载。获取到指定接口的全部实现类的类名称,调用SpringFactoriesLoader#instantiateFactory
进行实例化。
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
优缺点
- Spring的SPI机制对Java的SPI机制对进行了一些简化,Java的SPI每个接口都需要对应的文件,而Spring的SPI机制只需要一个spring.factories文件。
- Java的SPI机制文件内容必须为接口的实现类,而Spring的SPI并不要求键值对必须有什么关系,更加灵活。
Dubbo SPI机制–ExtensionLoader
ExtensionLoader
是dubbo的SPI机制的实现类。每一个接口都会有一个自己的ExtensionLoader
实例对象,这点跟Java的SPI机制是一样的。
同样地,Dubbo的SPI机制也做了以下几点约定:
- 接口必须要加@SPI注解
- 配置文件可以放在
META-INF/services/
、META-INF/dubbo/internal/
、META-INF/dubbo/
、META-INF/dubbo/external/
这四个目录底下,文件名也是接口的全限定名 - 内容为键值对,键为短名称(可以理解为spring中Bean的名称),值为实现类的全限定名
demo展示
引入依赖
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.4.1</version>
</dependency>
接口类
@SPI
public interface LoadBalance {
}
实现类
public class RandomLoadBalance implements LoadBalance {
public RandomLoadBalance() {
System.out.println("random init ...");
}
}
实现类
@Adaptive
public class RoundLoadBalance implements LoadBalance{
}
在META-INF\dubbo
创建文件名为com.charles.spi.LoadBalance
的文件
random=com.charles.spi.RandomLoadBalance
round=com.charles.spi.RoundLoadBalance
测试效果
public class DubboSpiTest {
public static void main(String[] args) {
ExtensionLoader<LoadBalance> extensionLoader = ExtensionLoader.getExtensionLoader(LoadBalance.class);
LoadBalance random = extensionLoader.getExtension("random");
LoadBalance adaptiveExtension = extensionLoader.getAdaptiveExtension();
System.out.println(random);
System.out.println(adaptiveExtension);
}
}
通过ExtensionLoader
的getExtension
方法,传入短名称,这样就可以精确地找到短名称对的实现类。Dubbo的SPI机制解决了前面提到的无法获取指定实现类的问题。
源码解析
ExtensionLoader#getExtension
,根据名称获取实现类名,进行初始化。
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
ExtensionLoader#loadExtensionClasses
,加载指定目录下面的文件。
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
ExtensionLoader#loadDirectory
,加载指定目录下的文件(META-INF/dubbo/com.charles.spi.LoadBalance
)
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
ExtensionLoader#loadResource
,读取文件。
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
ExtensionLoader#loadClass
,加载类
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
ExtensionLoader#cacheAdaptiveClass
,判断是否Adaptive
注解,将类赋值给cachedAdaptiveClass
。
private void cacheAdaptiveClass(Class<?> clazz) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getName()
+ ", " + clazz.getName());
}
}
ExtensionLoader#saveInExtensionClass
,保存结果在extensionClasses
。
private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name) {
Class<?> c = extensionClasses.get(name);
if (c == null) {
extensionClasses.put(name, clazz);
} else if (c != clazz) {
String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName();
logger.error(duplicateMsg);
throw new IllegalStateException(duplicateMsg);
}
}
获取自适应实现类,如果没有,进行创建。
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
ExtensionLoader#getAdaptiveExtensionClass
,执行getExtensionClasses
进行类加载,获取cachedAdaptiveClass
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}