4. Dubbo扩展机制 - 基础应用
1. JAVA SPI
SPI
是英文Service Provider Interface
的缩写,是一种服务发现机制。SPI
是一种可插拔机制,需要定义一个接口,然后针对不同的场景进行实现,体现了我们常说的面向接口编程思想。
JAVA
自带的SPI
机制是JDK1.6
引入的,下面来看一个示例,了解下SPI
的使用方式:
首先定义一个接口
public interface Mascot {
String getName();
}
针对这个接口创建两个实现类
public class YoYo implements Mascot{
@Override
public String getName() {
return "YoYo";
}
}
public class Click implements Mascot {
@Override
public String getName() {
return "Click";
}
}
在resources
目录创建文件夹META-INF/services
,然后创建一个名字问接口名的文件cn.juejin.spi.Mascot
,内容为两个类的全名。
cn.juejin.spi.Click
cn.juejin.spi.YoYo
好了,创建一个启动类来启动测试下吧
public class Main {
public static void main(String[] args) {
ServiceLoader<Mascot> mascots = ServiceLoader.load(Mascot.class);
for (Mascot mascot : mascots) {
System.out.println(mascot.getName());
}
}
}
右键运行,结果如下:
JAVA SPI
是通过ServiceLoader
来加载接口对应的实现类,但是他会将文件里的所有类都进行加载进来实例化,并不能按需加载。
可能我们并没有这样使用过JDK的SPI
,但是我们间接的使用过,比如JDK中大名鼎鼎的java.sql.Driver
接口,当我们引入MySQL
的jar包时,JDK就会加载MySQL
的实现类,当我们引入Oracle
的jar包时,他就会加载Oracle
的实现类。这就是通过SPI类实现的。
2. Dubbo SPI
JAVA SPI
有个很明确的缺点是,不能按需加载,接口对应的文件里有多少个类,就会实例化多少。通常情况下,我们更希望的是加载一个指定的类。在配置文件里,为每一个类定义一个key,需要加载的时候,指定这个key就好了。
click=cn.juejin.spi.Click
yoyo=cn.juejin.spi.YoYo
JAVA SPI
不能满足这样的功能,所以Dubbo自己实现类一套SPI机制,即Dubbo SPI
。
我们都知道Dubbo支持多种协议:dubbo
、http
、redis
、rmi
、rest
、thrift
等。我们需要哪些协议,只需要在配置文件里指定就好了,这就是通过Dubbo SPI
来实现的。
下面我们来看下一段获取http协议的代码:
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = extensionLoader.getExtension("dubbo");
System.out.println(protocol);
其中Protocol
是一个协议接口,dubbo支持的协议都实现了这个接口。ExtensionLoader
是一个扩展点加载器,上面例子第一行是说,获取一个Protocol
的扩展点的扩展点加载器,dubbo
、http
、redis
这些协议就是Protocol
的扩展点。第二行是说,要获取dubbo
这个具体的扩展点。运行之后会获取dubbo
对应的一个扩展点实现类。
好了,这是Dubbo SPI
的一个简单用法,知道了怎么用之后,那我们把文中第一个示例改造一下,使用Dubbo SPI
来实现吧。
首先,在接口上加上@SPI
注解。
@SPI
public interface Mascot {
String getName();
}
在resources
目录创建文件夹META-INF/dubbo
,然后创建一个名字问接口名的文件cn.juejin.spi.Mascot
,内容如下。
ini复制代码click=cn.juejin.spi.Click
yoyo=cn.juejin.spi.YoYo
改造一下Main函数,并运行看一下吧。
我们指定了,要获取yoyo
这个扩展点,然后输出了YoYo
这个类的实例化对象。
3. SPI AOP
Dubbo SPI
只是做了这些功能吗?可以指定一个key进行加载?当然不是啦,Dubbo SPI
还支持了类似AOP
的功能,可以实现在一个类中注入另一个对象。
我们为Mascot
接口创建一个Wrapper
类
public class MascotWrapper implements Mascot {
private final Mascot mascot;
public MascotWrapper(Mascot mascot) {
this.mascot = mascot;
}
@Override
public String getName() {
System.out.println("MascotWrapper.....");
return mascot.getName();
}
}
修改META-INF/dubbo/cn.juejin.spi.Mascot
文件内容
click=cn.juejin.spi.Click
yoyo=cn.juejin.spi.YoYo
cn.juejin.spi.MascotWrapper
现在我们再次运行刚才的Main函数,会输出什么呢?还是YoYo吗?来看一下:
我们可以看到,虽然我们获取的是
yoyo
,但实际上输出的是MascotWrapper
,但MascotWrapper
里面的mascot
属性值却是yoyo
,这就是Dubbo SPI
提供的类似AOP的功能,或者叫做依赖注入。当然,如果你愿意,也可以创建多个Wrapper
,比如MascotWrapperWrapper
、MascotWrapperWrapperWrapper
。。。
另外,这个命名也不一定飞叫Wrapper
,名字是可以随便起的,你也可以叫MascotRap
,dubbo是根据这个类的构造器来确定的。
4. 源码分析
ExtensionLoader<Mascot> extensionLoader
= ExtensionLoader.getExtensionLoader(Mascot.class);
这一行代码比较简单,获取Mascot接口对应的扩展点加载器。
getExtensionLoader
方法,首先是一堆判断,可以先忽略,先从map
缓存里获取接口类对应的扩展点加载器,如果缓存里没有,那就创建一个,然后放到缓存里,再返回。
Mascot yoyo = extensionLoader.getExtension("yoyo");
这一行代码就是要获取一个具体的扩展点了,看下具体的实现。这段代码也比较简单,先判断是否为默认扩展点,如果是返回默认的扩展点,否则,判断缓存中是否已经创建了扩展点,如果没有创建过则创建对应的扩展点并放入缓存中,然后返回。
如果name
的值是true
,将得到一个默认的扩展点,那什么是默认的扩展点呢?我们来试一下。
首先对接口进行改造:
@SPI("yoyo")
public interface Mascot {
String getName();
}
把那name
值改为true
,运行一下,我们可以看到依然获取到了YoYo这个默认的对象。
继续看源码,getOrCreateHolder
获取class对应的holder对象,这个对象作为一个锁,跟并发有关系,防止重复生成。
createExtension
方法是创建扩展点。
getExtensionClasses
方法是获取解析配置文件,查找所有的扩展点并缓存起来。 查找配置文件的路径为META-INF/dubbo/internal/
、META-INF/dubbo/
、META-INF/services/
。查找到之后,把对应的key
和类class
文件放到map
中。再根据传进来的name
获取对应的class
文件,并进行实例化。
injectExtension(instance);
对实例化后的对象,看里面有哪些属性并进行赋值。
这里就是我们刚才写的Wrapper
类,这里遍历拿到的所有包装类,根据传过来的type寻找对应的构造器,并进行实例化。实例化后再对包装类的属性进行注入。
这里就是对Dubbo SPI
获取扩展点的简单源码解析。
作者:Java星辰 链接:https://juejin.cn/post/7171829536751878152