SPI,在dubbo的官网上是这么介绍的,SPI全称为Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。
dubbo官网
JAVA的SPI机制
使用
- 首先需要一个目录META-INF/services
- 建一个文件,文件名字是接口的名字
- 文件内容是某个接口实现类的全限定类名
- 我们现在通过模拟一个数据库驱动Driver的spi
- 创建一个MyDriver,实现java.sql.Driver
- 在resources目录下新建META-INF/services/java.sql.Driver文件
- java.sql.Driver中的内容是MyDriver的全限定类名
- 编写测试类进行测试
public class JavaSpi {
public static void main(String[] args) {
ServiceLoader<Driver> load = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = load.iterator();
while (iterator.hasNext()){
Driver driver = iterator.next();
System.out.println(driver.getClass().getName());
//输出内容:com.test.spi.java.MyDriver
}
}
}
源码分析
- 我们从ServiceLoader.load开始看起,一路看下去可以看到创建了一个ServiceLoader实例,并且在构造器中执行了reload()方法,初始化了一个LazyIterator,这个LazyIterator中的方法很关键
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(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();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
- 然后我们在遍历的时候,会得到一个迭代器,这个迭代器是在ServiceLoader内部new出来的
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;
//最终我们会走到在reload()方法中创建的LazyIterator类中
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
//同理,这里也会进入到LazyIterator中
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
- LazyIterator的hasNext()->hasNextService()->parse()
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);
}
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//这里就是去读取文件
//private static final String PREFIX = "META-INF/services/"
//service就是我们调用load方法传进来的Driver,最终得到一个路径名
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());
}
//我们调用next()获取到的类名
nextName = pending.next();
return true;
}
- LazyIterator的next()->nextService()
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
}
spring的SPI机制
使用
- 新建一个META-INF/spring.factories文件
- spring.factories的内容是key-value的形式,key是某个类的全限定类名,value也是某个类的全限定类名,如果只获得value的全限定类名,是不需要key和value有父子关系的,即调用loadFactoryNames方法,下面会说明
- 模仿springboot的EnableAutoConfiguration的创建一个注解
- 随便写一个类
- 写spring.factories文件
- 编写测试类
public class SpringSpi {
public static void main(String[] args) {
//这里loadFactoryNames只是获得MyConfiguration为key的所有value的名称,并没有创建实例
List<String> strings = SpringFactoriesLoader.
loadFactoryNames(MyConfiguration.class, SpringTestClass.class.getClassLoader());
System.out.println(strings);
//输出内容:[com.test.spi.spring.SpringTestClass]
}
}
源码
- 我们从loadFactoryNames方法看起,调用loadSpringFactories
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//缓存中有,就直接返回
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//这里和java的spi是一样的,都是去读取文件,
//FACTORIES_RESOURCE_LOCATION= "META-INF/spring.factories"
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//得到我们的key和value的形式 比如我们上面的例子的 key=com.test.spi.spring.MyConfiguration
//value=com.test.spi.spring.SpringTestClass
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
//我们的value可以是以逗号分割的字符串,这里变成一个数据
for (String factoryImplementationName :
StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
//加入Map中
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
//加入缓存,返回
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
Dubbo的SPI机制
dubbo的SPI可以分为静态扩展点,自适应扩展点和激活扩展点。
使用
- 自定义一个Container
- 在resources下新建META-INF/services/org.apache.dubbo.container.Container
- 在org.apache.dubbo.container.Container文件中写入myContainer=com.test.spi.dubbo.MyContainer
- 写测试类,只有静态扩展点我们需要新建类,其余的都是dubbo自身有的类
public class DubboSpi {
public static void main(String[] args) {
//静态扩展点myContainer 就是我们在文件中的key
Container myContainer = ExtensionLoader.getExtensionLoader(Container.class).getExtension("myContainer");
System.out.println(myContainer);
//输出:com.test.spi.dubbo.MyContainer@6aa8ceb6
//自适应扩展点,加在类上和加载方法上
Compiler adaptiveExtension = ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension();
System.out.println(adaptiveExtension);
//输出:org.apache.dubbo.common.compiler.support.AdaptiveCompiler@759ebb3d
Protocol adaptiveExtension1 = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
System.out.println(adaptiveExtension1);
//输出:org.apache.dubbo.rpc.Protocol$Adaptive@61a485d2
//激活扩展点
URL url = new URL("","localhost",8080).addParameters(Constants.DEPRECATED_KEY,Constants.DEPRECATED_KEY);
List<Filter> activateExtension = ExtensionLoader.getExtensionLoader(Filter.class)
.getActivateExtension(url, Constants.DEPRECATED_KEY);
System.out.println(activateExtension);
//输出:[org.apache.dubbo.rpc.filter.EchoFilter@69ea3742, org.apache.dubbo.rpc.filter.ClassLoaderFilter@4b952a2d, org.apache.dubbo.rpc.filter.GenericFilter@3159c4b8, org.apache.dubbo.rpc.filter.ConsumerContextFilter@73846619, org.apache.dubbo.rpc.filter.ContextFilter@4bec1f0c, org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter@29ca901e, org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter@5649fd9b, org.apache.dubbo.rpc.filter.TimeoutFilter@6adede5, org.apache.dubbo.monitor.support.MonitorFilter@2d928643, org.apache.dubbo.rpc.filter.ExceptionFilter@5025a98f, org.apache.dubbo.cache.filter.CacheFilter@49993335]
}
}
源码
- 我们从ExtensionLoader.getExtensionLoader().getExtension()方法进入源码
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
//检查是否被加载过,如果有就直接返回,没有就进入createExtension方法,进行创建
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) {
//会进入到getExtensionClasses()去加载所有的扩展点
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
.... 这里省略依赖注入和包装类的方法
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;
}
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
//从这里我们可以看到,dubbo支持在很多目录下读文件
Map<String, Class<?>> extensionClasses = new HashMap<>();
//下面会进入到loadDirectory方法
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;
}
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);
}
}
- 分析到上面,我们的静态扩展点就搞定了,剩下的自适应扩展点和激活扩展点在这里