下面我们看下JDK中
ServiceLoader<S>
方法的具体实现:文章插图
首先,ServiceLoader实现了
Iterable
接口,所以它有迭代器的属性,这里主要都是实现了迭代器的hasNext
和next
方法 。这里主要都是调用的lookupIterator
的相应hasNext
和next
方法,lookupIterator
是懒加载迭代器 。其次,
LazyIterator
中的hasNext
方法,静态变量PREFIX就是”META-INF/services/”
目录,这也就是为什么需要在classpath
下的META-INF/services/
目录里创建一个以服务接口命名的文件 。最后,通过反射方法
Class.forName()
加载类对象,并用newInstance
方法将类实例化,并把实例化后的类缓存到providers
对象中,(LinkedHashMap<String,S>
类型)然后返回实例对象 。所以我们可以看到
ServiceLoader
不是实例化以后,就去读取配置文件中的具体实现,并进行实例化 。而是等到使用迭代器去遍历的时候,才会加载对应的配置文件去解析,调用hasNext
方法的时候会去加载配置文件进行解析,调用next
方法的时候进行实例化并缓存 。所有的配置文件只会加载一次,服务提供者也只会被实例化一次,重新加载配置文件可使用
reload
方法SPI机制的缺陷通过上面的解析,可以发现,我们使用SPI机制的缺陷:
- 不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现 。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费 。
- 如果扩展点加载失败,连扩展点的名称都拿不到了 。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因 。
- 多个并发多线程使用 ServiceLoader 类的实例是不安全的 。
Dubbo SPI 优势
- 按需加载,Dubbo SPI配置文件采用KV格式存储,key 被称为扩展名,当我们在为一个接口查找具体实现类时,可以指定扩展名来选择相应的扩展实现,只实例化这一个扩展实现即可,无须实例化 SPI 配置文件中的其他扩展实现类,避免资源浪费,此外通过 KV 格式的 SPI 配置文件,当我们使用的一个扩展实现类所在的 jar 包没有引入到项目中时,Dubbo SPI 在抛出异常的时候,会携带该扩展名信息,而不是简单地提示扩展实现类无法加载 。这些更加准确的异常信息降低了排查问题的难度,提高了排查问题的效率 。;
- 增加扩展类的 IOC 能力,Dubbo 的扩展能力并不仅仅只是发现扩展服务实现类,而是在此基础上更进一步,如果该扩展类的属性依赖其他对象,则 Dubbo 会自动的完成该依赖对象的注入功能;
- 增加扩展类的 AOP 能力,Dubbo 扩展能力会自动的发现扩展类的包装类,完成包装类的构造,增强扩展类的功能;
文章插图
主要步骤为 4 个: