前言
学习了解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 {@Overridepublic 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();//调用方法loadreturn 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,这里会进行缓存。
//缓存所有的Loaderprivate 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是AdaptiveExtensionFactoryobjectFactory = (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的classClass<?> 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的IOCinjectExtension(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<>();//循环从以下地址的文件路径上去加载出来这些classfor (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 firstif (extensionLoaderClassLoaderFirst) {ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {urls = extensionLoaderClassLoader.getResources(fileName);}}//加载出这些文件的urlsif(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,并加载这些classloadClass(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,或使用小写的类名作为 namename = 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注解对应的cachedActivatescacheActivateClass(clazz, names[0]);for (String n : names) {//缓存到cachedNames当中去cacheName(clazz, n);//将这个class放到参数extensionClasses当中去saveInExtensionClass(extensionClasses, clazz, n);}}}}
经过了以上的步骤,我们就拿到了这个类了,并且这个类还被注入过了。
Loader缓存的数据
当我们按照上述的方法获取该class之后,我们的ExtensionLoader 也缓存了不少数据了。
//总缓存的Loader,以type为keyprivate 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<>();//缓存的wrapperprivate Set<Class<?>> cachedWrapperClasses;
以上就是一个type的加载过程,这里面还有Adaptive 的了解,factory的加载,wrapper的处理,后续讲解下。
