前言
学习了解dubbo,第一步的基础就是了解spi相关的。知道dubbo怎么来加载这些类。像Spring可以使用注解来实现自己的容器,进行IoC控制所有的类。dubbo采取了另一个方式来管理它所需要的所有的类。这就是使用SPI机制。
SPI介绍
SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。java原生有SPI机制,
Java SPI
介绍dubbo spi之前,我们先了解java的spi。dubbo的spi就是创建一个接口和实现类,然后在resource目录下,建立 META-INF/services/xxx
的文件,xxx是接口名称,文件里面写上类全量名即可。类定义如下:
public interface Robot {
void sayHello();
}
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("amazing...");
}
}
文件名: META-INF/services/com.lml.part.dubbo.spi.Robot
,内容如下:
com.lml.part.dubbo.spi.Bumblebee
使用如下:
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("java spi. ");
for (Robot robot : serviceLoader) {
robot.sayHello();
}
Java SPI源码
首先创建出来 ServiceLoader
这里没什么特别的,主要是创建了 LazyIterator
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
//调用方法load
return ServiceLoader.load(service, cl);
}
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();
//主要加载了LayIterator类,所有的实现也在这里
lookupIterator = new LazyIterator(service, loader);
}
获取的时候,进行 iterator
循环迭代获取,iterator
调用的是 providers
的循环,最后调用的是 LazyIterator
。以下是读取所有的文件返回出文件名称集合。
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//PREFIX前缀:META-INF/services/
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();
}
以下是加载出来class文件。
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) {
//没找到class报错
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//进行cast转化
S p = service.cast(c.newInstance());
//将加载的class,使用providers缓存起来
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
java spi的缺点是:
- iterator 虽然进行了延迟加载,但是加载时,也是依旧读取文件将所有的class进行加载出来了,这里还是有些浪费性能的。
- 获取class的时候,只能通过iterator形式获取。
Dubbo SPI介绍
dubbo基于这种形式,实现了一套更强的SPI机制,它将所有的类封装在了ExtensionLoader
类中,通过 ExtensionLoader
,去加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo
路径下,文件名还是和java spi一样,是加载接口的文件名,文件格式是key,value形式的,格式如下:
bumblebee = org.apache.spi.Bumblebee
另外加载的接口必须标记 SPI
注解才能使用,使用方式如下。
ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
SPI标记类如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
/**
* default extension name
*/
String value() default "";
}
说明
dubbo的spi实现的功能更为强大,不仅仅实现了刚刚类的加载,同时,它将加载的类缓存起来,我们再次获取时,将直接从缓存获取返回,这样相当于实现了简单的容器。容器里面所有的元素,我们都能拿到了。
当我们有了容器之后,对于我们加载的接口实现也就能实现一定的依赖注入了。同时,能够拿到接口的实现类,也可以进行AOP包装拦截了,这个可以一步步说明。
每一个接口都有很多的实现,这些实现都被封装进入同一个ExtensionLoader
了, 既然要获取接口实现类,首先就要获取到接口的ExtensionLoader
实现类,这个接口的loader会缓存接口加载的所有实现类,默认激活类等等。
获取当前接口ExtensionLoader
之前,我们还需要首先获取loader加载的factory,用来加载所有标记SPI
的接口,这个后续讲解。
getExtensionLoader
首先获取loader,这里会进行缓存。
//缓存所有的Loader
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
//验证必须标记了SPI接口信息
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
//缓存中获取接口的Loader,找不到进行创建
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
//每一个Loader的扩展工厂,用于实现加载class时的setting方法注入
private final ExtensionFactory objectFactory;
private ExtensionLoader(Class<?> type) {
this.type = type;
//设置扩展工厂,这里最后加载的objectFactory是AdaptiveExtensionFactory
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
getExtension(String name)
获取指定xxx的class,这里的xxx就是dubbo文件中key-value对应的key,这样就获取到了接口的实现类。
//缓存的接口实现类。key=xxx,holder是持有的实例
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
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;
}
这里依旧先从当前loader的缓存中获取,获取不到时,进行创建。
createExtension(String name)
这里进行接口的创建工作。
//缓存的所有通过ExtensionLoader加载好的实例
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
//缓存所有的wrapper类
private Set<Class<?>> cachedWrapperClasses;
private T createExtension(String name) {
//这里是从配置文件中加载所有的拓展类集合,拿到这个扩展类集合后,再继续拿到指定key的class
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//检查class是否存在,通过反射实例化
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//向实例中注入依赖,依赖上面的ExtensionFactory 这里就是Dubbo的IOC
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
//如果发现是wrapper类,开始注入加载
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
//将拿到的instance注入到wrapper类当中去,然后将wrapper类再次赋值给当前的instance
//这里是dubbo的AOP体现
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
//确定下这个instance是否继承了Lifecycle,帮它进行初始化工作
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
通过这样的方式,就将配置文件中加载的class实例化出来,并且返回了。这里需要关注下,首先getExtensionClasses配置文件加载的方法,这里加载了所有的class。其次是,injectExtension进行了注入,如何注入的,作用是什么。最后是,wrapperClasses的处理和用处。
getExtensionClasses()
加载获取该接口所有的class
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//缓存下这些class,真正的加载地方
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
//dubbo默认加载的路径
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 static LoadingStrategy[] strategies = new LoadingStrategy[] { DUBBO_INTERNAL_STRATEGY, DUBBO_STRATEGY, SERVICES_STRATEGY };
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
//循环从以下地址的文件路径上去加载出来这些class
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
}
return extensionClasses;
}
loadDirectory
加载这些路径的文件,主要就是读取这些路径的文件,然后逐条解析下,之后进行分类加载。
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, String... excludedPackages) {
// fileName = 文件夹路径 + type 全限定名
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader();
// try to load from ExtensionLoader's ClassLoader first
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}
//加载出这些文件的urls
if(urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
//进一步将拿到的url进行解析
loadResource(extensionClasses, classLoader, resourceURL, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, String... excludedPackages) {
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 && !isExcluded(line, excludedPackages)) {
//反射这个class,并加载这些class
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);
}
}
loadClass
这里就是class详细区分和加载。
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
//必须是当前type的实现类
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)) {
// 检测目标类上是否有 Adaptive 注解
// 设置 cachedAdaptiveClass缓存
cacheAdaptiveClass(clazz);
} else if (isWrapperClass(clazz)) {
// 检测 clazz 是否是 Wrapper 类型(这里检测方式就是这个类是有有一个此type的构造函数存在)
// 存储 clazz 到 cachedWrapperClasses 缓存中
cacheWrapperClass(clazz);
} else {
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 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)) {
// 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,
// 存储 name 缓存到Activate注解对应的cachedActivates
cacheActivateClass(clazz, names[0]);
for (String n : names) {
//缓存到cachedNames当中去
cacheName(clazz, n);
//将这个class放到参数extensionClasses当中去
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
经过了以上的步骤,我们就拿到了这个类了,并且这个类还被注入过了。
Loader缓存的数据
当我们按照上述的方法获取该class之后,我们的ExtensionLoader
也缓存了不少数据了。
//总缓存的Loader,以type为key
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
//总缓存的所有的class的实例
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
//当前Loader的数据情况
//当前接口
private final Class<?> type;
//当前接口对应的扩展factory,用于自动注入
private final ExtensionFactory objectFactory;
//此type下的class的名称
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
//此type下的class的实例
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
//缓存的activetes实例
private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
//缓存的wrapper
private Set<Class<?>> cachedWrapperClasses;
以上就是一个type的加载过程,这里面还有Adaptive
的了解,factory的加载,wrapper的处理,后续讲解下。