dubbo是如何实现可扩展的?
dubbo如何实现可扩展的,援引官网描述:
Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。
Dubbo 改进了 JDK 标准的 SPI 的以下问题:
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过
getName()
获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。 - 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
定义个接口:
public interface HelloService {
String sayHello();
}
定义两个实现类:
public class DogHelloService implements HelloService {
@Override
public String sayHello() {
return "wang";
}
}
public class HumanHelloService implements HelloService {
@Override
public String sayHello() {
return "hello 你好";
}
}
1.JDK标准的SPI是怎么回事?
ServiceLoader.load方法会加载META-INF/services/目录下定义的接口全限定名文件,内容为实现类。
com.exm.service.impl.DogHelloService
com.exm.service.impl.HumanHelloService
核心为ServiceLoader.LazyIterator迭代器,在load方法被调用时,会初始化该迭代器,如下:
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
LazyIterator会读取配置实现类,并通过反射进行实例化(前提要求实现类需要具备无参构造)。
其中hasNextService方法会加载META-INF/services接口文件,并加载到Enumeration<URL> configs中,源码如下:
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//获取配置全路径名。如:META-INF/services/com.exm.service.HelloService
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
//并加载到Enumeration<URL> configs中
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;
}
迭代器通过nextService获取下一个实现类对象,源码如下,其中包含反射拿到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) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//转强制化为接口,并放入LinkedHashMap<String,S> providers中
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
}
为什么说JDK的SPI会一次性加载并实例化所有的扩展呢?
因为在调load时,实际构造了ServiceLoader.LazyIterator,如果想找到某个扩展实现,需要迭代器遍历所有的实现才可以。
️如果其中有一个实例化或cast时异常,后边所有都将无法遍历。
️如果某个类的实例化耗时很长,并没用到,会造成资源浪费
编写一个测试方法:
public static void main(String[] args) {
final ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);
for (HelloService helloService : helloServices){
System.out.println(helloService.getClass().getName() + ":" + helloService.sayHello());
}
}
测试输出:
com.exm.service.impl.DogHelloService:wang
com.exm.service.impl.HumanHelloService:hello 你好
2.Dubbo是怎么进行改进的呢?
dubbo时如何进行改进的呢?
(1)dubbo定义的SPi文件包含了key,即每个实现类对应一个不同的key,在加载class的时候,会将key和class放入一个map中。
这样在使用者想使用哪个类的实例时,只需要实例化对应的类,无需实例化所有类
(2)Adaptive功能:实现动态的使用扩展点。通过 getAdaptiveExtension方法 统一对指定接口对应的所有扩展点进行封装,通过URL的方式对扩展点来进行动态选择。
2.1 加载所有扩展点,选择性实例化
public class Main {
public static void main(String[] args) {
// 获取扩展加载器
ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
// 遍历所有的支持的扩展点,并将key与实现类进行关联
Set<String> extensions = extensionLoader.getSupportedExtensions();
for (String extension : extensions){
String result = extensionLoader.getExtension(extension).sayHello();
System.out.println(result);
}
}
}
ExtensionLoader.getSupportedExtensions会加载所有扩展类(但没有实例化)。然后通过extensionLoader.getExtension对指定key进行实例化,这一点是与JDK不同的。
我们看下具体是怎么加载的,getSupportedExtensions为入口,最终会通过loadDirectory进行加载
Set<String> getSupportedExtensions()
Map<String, Class<?>> getExtensionClasses()
Map<String, Class<?>> loadExtensionClasses()
void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst)
(1)getSupportedExtensions方法会返回所有扩展点的key,供用户使用。
public Set<String> getSupportedExtensions() {
Map<String, Class<?>> clazzes = this.getExtensionClasses();
//获取到所有扩展点后,将key放入TreeSet中(按字符串排序)
return Collections.unmodifiableSet(new TreeSet(clazzes.keySet()));
}
loadDirectory加载文件的来源为以下6个部分,兼容了JDK路径。
同时加载有顺序,越靠前越优先加载
private Map<String, Class<?>> loadExtensionClasses() {
this.cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap();
this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName(), true);
this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/", this.type.getName().replace("org.apache", "com.alibaba"), true);
this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName());
this.loadDirectory(extensionClasses, "META-INF/dubbo/", this.type.getName().replace("org.apache", "com.alibaba"));
this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName());
this.loadDirectory(extensionClasses, "META-INF/services/", this.type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
(2)在Set中获取到扩点类对应的key,通过getExtension获取对应class的实例(包含通过setter进行依赖注入)
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
} else if ("true".equals(name)) {
return this.getDefaultExtension();
} else {
Holder<Object> holder = this.getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized(holder) {
instance = holder.get();
if (instance == null) {
//创建对应class的实例,完成依赖注入
instance = this.createExtension(name);
holder.set(instance);
}
}
} return instance;
}
}
createExtension方法是实例化的核心,实现了IOC和AOP,注释如下:
private T createExtension(String name) {
//获取name对应的class
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);
}
//依赖注入(IOC)
injectExtension(instance);
//包装器(AOP)
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
2.2 getAdaptiveExtension根据URL参数动态获取相应的扩展点
public class AdaptiveMain {
public static void main(String[] args) {
URL url = URL.valueOf("test://localhost/hello?hello.service=dog");
HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
String msg = adaptiveExtension.sayHello(url);
System.out.println(msg);
}
}
(1)核心代码为ExtensionLoader.getAdaptiveExtension方法
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;
}
(2)创建自适应扩展类实例
private T createAdaptiveExtension() {
try {
//获取自适应扩展类,并实例化,然后通过setter注入依赖
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
(3)生成自适应扩展类class
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
(4)加载类扩展点(与上文相同)
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;
}
(5)创建自适应扩展class,动态生成代码,并进行编译
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
得到如下class:
package com.exm.service;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class HelloService$Adaptive implements com.exm.service.HelloService {
public java.lang.String sayHello() {
throw new UnsupportedOperationException("The method public abstract java.lang.String com.exm.service.HelloService.sayHello() of interface com.exm.service.HelloService is not adaptive method!");
}
public java.lang.String sayHello(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
String extName = url.getParameter("hello.service", "human");
if(extName == null) throw new IllegalStateException("Failed to get extension (com.exm.service.HelloService) name from url (" + url.toString() + ") use keys([hello.service])");
com.exm.service.HelloService extension = (com.exm.service.HelloService)ExtensionLoader.getExtensionLoader(com.exm.service.HelloService.class).getExtension(extName);
return extension.sayHello(arg0);
}
}
可以看到自适应只支持具有adaptive注解的方法,并且参数汇总需要有URL参数。
具体逻辑,是通过获取URL参数中的变量,ExtensionLoader.getExtensionLoader().getExtension(name)获取具体的class实例,完成调用。
(6)通过setter注入依赖
private T injectExtension(T instance) { if (objectFactory == null) {
return instance;
} try {
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
} try {
String property = getSetterProperty(method);
//获取到Adaptive对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
} }
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
(7)上文中的objectFactory是ExtensionFactory实例,其实现类包含SpiExtensionFactory和SpringExtensionFactory,一个是dubbo的扩展工厂,一个是Spring的工厂;前者只支持type是SPI的接口,并生成自适应类;后者从Spring容器中获取。
在依赖注入时,会在两个容器中遍历,如下:
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
(8)另外,还有一个实现类是AdaptiveExtensionFactory是默认的@Adaptive类,即被该注解修饰的类是自适应类,就不会动态生成了。
在getExtensionClasses()加载ExtensionFactory扩展class时,如果扩点类被Adaptive注解修饰,则将缓存在ExtensionLoader.cachedAdaptiveClass中;
在getAdaptiveExtensionClass方法中,直接返回,不需要生成自适应类。
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.");
}
//判断类事都是Adaptive类,是的话就缓存,在getAdaptiveExtensionClass时直接返回
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);
}
}
}
}
综上,dubbo加载class扩展与实例化是分开的,可以通过指定key实例化某一个class;
dubbo支持IOC和AOP;
同时,dubbo结合SPI与Adaptive注解,可以实现对所有扩展class封装,然后根据URL参数动态获取指定的class。
3.在注入依赖的时候是否有循环依赖的问题?
在dubbo创建扩展class实例时,会通过setter进行依赖注入,如果存在循环依赖,怎么处理?
在dubbo依赖注入时,除了Spring容器外,从SPI容器中获取,获取的是SPI接口的自适应实现,是新创建的类,所以不存在循环依赖的问题。
牛逼的框架,就是让你一眼看不懂它在干什么 ---me
dubbo是如何实现可扩展的?的更多相关文章
- Dubbo源码分析系列---扩展点加载
扩展点配置: 约定: 在扩展类的jar包内,放置扩展点配置文件:META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔.(摘自dubbo文档) 示例: ...
- dubbo系列六、SPI扩展Filter隐式传参
一.实现Filter接口 1.消费者过滤器:ConsumerTraceFilter.java package com.dubbo.demo.Filter; import com.alibaba.dub ...
- Dubbo实践(八)扩展点装饰
Filter Filter是Dubbo里面非常重要的模块,Dubbo里面日志记录.超时等功能都是在这一部分实现. 如上一节在介绍扩展点加载时所述,在生成Protocol的invoker时,实际上使用了 ...
- Dubbo实践(五)扩展Spring Schema
先回顾Dubbo实践(一)中定义的dubbo-provider.xml: <?xml version="1.0" encoding="UTF-8"?> ...
- dubbo是如何实现可扩展的?(二)
牛逼的框架,看似复杂难懂,思路其实很清晰.---me 上篇文章,在整体扩展思路上进行了源码分析,比较粗糙,现在就某些点再详细梳理下. dubbo SPi的扩展,基于一类.三注解. 一类是Extensi ...
- dubbo扩展
dubbo源码版本:2.5.4 经统计,dubbo一共有31个扩展,如下: ------------------------------------------------------ META-IN ...
- Dubbo学习笔记6:Dubbo增强SPI与SPI中扩展点自动包装的实现原理
在Dubbo整体架构分析中介绍了Dubbo中除了Service和Config层为API外,其他各层均为SPI,为SPI意味着下面各层都是组件化可以被替换的,也就是扩展性比较强,这也是Dubbo比较好的 ...
- 从ExtensionLoader理解Dubbo扩展机制
Dubbo的扩展机制是怎么实现的?最简单的回答就是@SPI. Dubbo的插件化思路来源于Java SPI. JAVA SPI 机制 SPI的全名为Service Provider Int ...
- dubbo源码分析之基于SPI的强大扩展
https://blog.csdn.net/luoyang_java/article/details/86609045 Dubbo采用微内核+插件体系,使得设计优雅,扩展性强.那所谓的微内核+插件体系 ...
随机推荐
- Canvas 核心技术
最近项目需求中要写较多H5小游戏,游戏本身体量不是很复杂,主要是承载较多业务逻辑,所以决定用canvas来完成游戏部分.之前只是知道H5中有canvas这个东西,也知道它大概是画图的,但具体怎么用,还 ...
- python-杨辉三角形
[题目描述]输出n(0<n)行杨辉三角形,n由用户输入. [练习要求]请给出源代码程序和运行测试结果,源代码程序要求添加必要的注释. [输入格式]一行中输入1个整数n. [输出格式]输出n行杨辉 ...
- 自定义View的onDraw 函数不执行
解决办法: 在自定义的View 的构造方法中添加一句话: this.setWillNotDraw(false);解释:那么加这条语句的作用是什么?先看API: If this ...
- Ubuntu20.04中创建Pycharm桌面快捷方式
[Desktop Entry] Type=Application Name=Pycharm GenericName=Pycharm3 Comment=Pycharm3:The Python IDE E ...
- Ubuntu中hyperledger-fabric2.3.0环境搭建
系统环境 hyperledger-fabric在Ubuntu安装过程,fabric版本为2.3.0 首先安装相关软件 1.安装docker 直接参考下面这篇文档安装好docker-ce即可 Ubunt ...
- Java实现单链表的合并(保证数据的有序性)
一.思路 1.比较两个链表的大小 2.将小链表插入到大链表中 3.使用插入保证链表数据的有序性 二.核心代码 /** * 合并两个链表,并且按照有序合并 * @param singleLinkedLi ...
- linux设备管理之主设备号与次设备号
主设备号和次设备号 一个字符设备或者块设备都有一个主设备号和次设备号.主设备号和次设备号统称为设备号.主设备号用来表示一个特定的驱动程序.次设备号用来表示使用该驱动程序的其他设备.(主设备号和控制这类 ...
- 《java基础——对象的拷贝》
java基础--对象的拷贝 一.浅拷贝: 规则: 1. 浅拷贝只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化. 2. 浅拷贝相当于两个对象共用一套实例. 格式: 类名 对象 ...
- Revit二次开发之创建风管
在Revit中,风管用于连接管件,风道末端和机械设备,今天简单尝试了下使用RevitAPI创建风管,现分享下我的方法. 风管从类型上可分为三类:一般风管,软风管和风管占位符:从形状上也分为三类 ...
- 基础的CSS描绘测试
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...