ExtensionLoader 表示某个接口的扩展点加载器,可以用来加载某个扩展点实例。
在ExtensionLoader中有几个非常重要的属性:
- ConcurrentHashMap EXTENSION_INSTANCES:用来缓存某个接⼝类型所对应的 ExtensionLoader实例
- Class type:表示当前 ExtensionLoader实例是哪个接口的扩展点加载器
- ExtensionFactory objectFactory:扩展点工厂(对象工厂),可以获得某个对象
ExtensionLoader和 ExtensionFactory的区别在于:
- ExtensionLoader最终所得到的对象是Dubbo SPI机制产⽣的
- ExtensionFactory最终所得到的对象可能是Dubbo SPI机制所产⽣的,也可能是从Spring容器中所获得的对象
ExtensionLoader中有三个常用的方法:
//表示获取名字为dubbo的扩展点实例
ExtensionLoader.getExtensionLoader(xxx.class).getExtension("dubbo")
//表示获取⼀个自适应的扩展点实例
ExtensionLoader.getExtensionLoader(xxx.class).getAdaptiveExtension()
//表示⼀个可以被url激活的扩展点实例
ExtensionLoader.getExtensionLoader(xxx.class).getActivateExtension(URL url, String[] values, String group)
二、Dubbo SPI源码分析
注意:
源码中大量使用了缓存,先取缓存,如果没有,再获取,并放到缓存中。
这里使用 Dubbo 中的 Protocol接口扩展点来分析。
@Test
public void testProtocol() {
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol dubbo = extensionLoader.getExtension("dubbo");
System.out.println("dubbo 指定名称的扩展点:" + dubbo);
}
先看一下它的在源码中的配置文件。
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
Exporter export(Invoker var1) throws RpcException;
@Adaptive
Invoker refer(Class var1, URL var2) throws RpcException;
void destroy();
}
进入 getExtensionLoader方法。
注意:
这个返回的 ExtensionLoader实例对象是 new出来的。
ConcurrentMap> EXTENSION_LOADERS = new ConcurrentHashMap();
每一个 SPI的扩展点接口都会对应一个 ExtensionLoader实例对象。
- key为接口的全限定名
- 值为ExtensionLoader实例对象
通过 SPI配置文件中 key=value, 的 key来获取实现类。
- 在调用 getExtension方法来获取⼀个扩展点实例后,会对实例进行缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。 但是,有可能获取到的不是实现类本身,而是实现类的包装类。
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
// 获取默认扩展类
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder holder = getOrCreateHolder(name);
Object instance = holder.get();
// 如果有两个线程同时来获取同一个name的扩展点对象,那只会有一个线程会进行创建
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建扩展点实例对象
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
Holder是一个包装,用来存放任何类型值的类。
查看 createExtension重点方法。
private T createExtension(String name) {
// 获取扩展类 {name: Class} key-Value 接口的所有实现类
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 wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class wrapperClass : wrapperClasses) {
// new XxxWrapper(instance)---包装类实例
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
在调用 createExtension(String name)方法来创建⼀个扩展点实例时,要经过以下几个步骤:
- 根据name找到对应的扩展点实现类
- 根据实现类生成⼀个实例,把实现类和对应生成的实例进行缓存
- 对生成出来的实例进行依赖注入(给实例的属性进行赋值)
- 对依赖注入后的实例进行AOP(Wrapper),把当前接口类的所有的Wrapper全部⼀层⼀层包裹在实例对象上,每包裹个Wrapper后,也会对Wrapper对象进行依赖注入。
- 返回最终的Wrapper对象
加载SPI配置文件,获取扩展类 {name: Class} key-Value 接口的所有实现类。
(1)查看getExtensionClasses 方法
getExtensionClasses()是⽤来加载当前接⼝所有的扩展点实现类并进⾏缓存,次需要加载时直接拿缓存中的返回⼀个Map。然后可以从这个 Map中按照指定的name获取对应的扩展点实现类。
(2)查看 loadResource方法
查看 loadDirectory方法 => 循环 loadResource方法。 loadResource方法就是完成对⽂件内容的解析,按⾏进⾏解析,会解析出"="两边的内容。
- "="左边的内容,就是扩展点的name,
- 右边的内容就是扩展点实现类,并且会利用ExtensionLoader类的类加载器来加载扩展点实现类。
然后调用 loadClass方法对name和扩展点实例进行详细的解析,并且最终把他们放到 Map中去。
(3)查看 loadClass方法
查看 loadDirectory方法 => 循环 loadResource方法 =>loadClass方法。
针对配置文件中的写法,大概有三种处理方式。
- 当前扩展点实现类上是否存在@Adaptive注解,如果存在则把该类认为是当前接⼝的默认⾃适应类(接口代理类),并把该类存到 cachedAdaptiveClass属性上。
- 当前扩展点实现是否是⼀个当前接口的⼀个Wrapper类
- 当前扩展点实现类是否有无参数的构造方法,如果有多个name,则判断⼀下当前扩展点实现类上是否存在@Activate注解,如果存在,则把该类添加到cachedActivates中,cachedWrapperClasses是⼀个map。最后,遍历多个name,把每个name和对应的实现类存到extensionClasses中去,extensionClasses 就是上⽂所提到的map。
至此,加载类就走完了。
2.2.2 Dubbo中的IOC查看 injectExtension(instance)方法。
遍历 包装类,包装套娃。
// AOP
Set wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class wrapperClass : wrapperClasses) {
// new XxxWrapper(instance)---包装类实例
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
3、getAdaptiveExtension()方法
自适应扩展点对象,也就是某个接口的代理对象是通过Dubbo内部生成代理类,然后生成代理对象的。
在Dubbo中,这种机制就是可以通过@Adaptive注解来指定某个类为某个接口的代理类,如果指定了,Dubbo在生成自适应扩展点对象时实际上生成的就是 @Adaptive注解所注解的类的实例对象。 如果未指定,由Dubbo默认实现的,生成代理类的。
3.1 创建接口实例的核心方法进入 getAdaptiveExtension()方法 =》 createAdaptiveExtension核心方法。
createAdaptiveExtensionClass方法就是Dubbo中默认生成 Adaptive类实例的逻辑。这个实例就是当前这个接口的⼀个代理对象。
3.1.1 先看 getAdaptiveExtensionClass方法 (1)进入getExtensionClasses方法
这个方法主要做的是,加载配置文件(通过流的方式),建立映射关系,loadExtensionClasses方法前面看过了。
(2)进入 createAdaptiveExtensionClass方法
进入 createAdaptiveExtensionClass方法。动态编译生成字符串类(字节重组),生成 .class文件加载到 JVM中并返回。
AdaptiveClassCodeGenerator生成接好的字符串类,最后,通过 compile加载到 JVM内存,并返回Class对象。
这个 查看 injectExtension方法。Dubbo中的IOC,前面看过了。
4、getActivateExtension方法Activate扩展点: 每个扩展点都有⼀个name,通过这个name可以获得该name对应的扩展点实例,但是有的场景下,希望⼀次性获得多个扩展点实例。比如示例:
@Test
public void test2() {
ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
URL url = new URL("http://", "localhost", 8080);
url = url.addParameter("cache", "test");
List activateExtensions = extensionLoader.getActivateExtension(url, new String[] { "validation" }, CommonConstants.CONSUMER);
for (Filter activateExtension : activateExtensions) {
System.out.println(activateExtension);
}
}
public List getActivateExtension(URL url, String[] values, String group) {
List exts = new ArrayList();
List names = values == null ? new ArrayList(0) : Arrays.asList(values);
// 想要获取的filter的名字不包括"-default"
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
// 缓存了被Activate注解标记了的类(已经被加载进来了的前提下),表示这个扩展点在什么时候能用
// 遍历所有的Activate的扩展类
for (Map.Entry entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
// group表示想要获取的Filter所在的分组,activateGroup表示当前遍历的Filter所在的分组,看是否匹配
// names表示想要获取的Filter的名字,name表示当前遍历的Filter的名字
// 如果当前遍历的Filter的名字不在想要获取的Filter的names内,并且names中也没有要排除它,则根据url看能否激活
if (isMatchGroup(group, activateGroup)
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
// 查看url的参数中是否存在key为activateValue,并且对应的value不为空
&& isActive(activateValue, url)) {
exts.add(getExtension(name));
}
}
exts.sort(ActivateComparator.COMPARATOR);
}
// 直接根据names来获取扩展点
List usrs = new ArrayList();
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?