从这篇起,开始进入Dubbo的源码篇,源码这部分,我准备主要分析Dubbo的服务注册和发布以及服务的调用。该篇是Dubbo源码的第一篇,我们来介绍下Dubbo里面的核心——SPI机制,为后续分析Dubbo其他代码做准备。那么这篇会学到如下的内容:
- SPI机制简介
- Java中的SPI机制
- Dubbo中的SPI机制
0x01 SPI机制简介
- SPI是什么?
- SPI英文全称是
Service Provider Interface
,它是一种服务的发现机制,简单点说它就是一个扩展点,是给其他人去实现,去扩展的。它是一种动态替换发现机制。 - 举个例子,我定义了一个接口,但是我本身不实现这个接口,这个接口的实现交给第三方厂商进行,我在运行时需要动态的得到这个接口的实现类(肯定会引入第三方的jar包),这时候该怎么做呢?那我可以定义个规则,这个实现类必须放到classpath下的某个文件夹下,并且要以我这个接口的全类名作为文件名新建一个文件,然后在这个文件中写上我们实现类的全类名。然后我在调用这个实现类的时候,就可以找到对应的目录下的对应文件,再用反射进行加载,不就在运行时动态的得到了改接口的实现类了么?这整个过程就是SPI.
- 目前在市面上大多数框架都是用它来作为服务的扩展发现,拿JDBC来说,JDK中定义了Driver接口,用来获取数据库的驱动,但是这个Driver接口的实现是不由JDK来管的,因为市面上数据库太多了,交给JDK去实现太复杂了。怎么办呢?那就每个厂商自己实现吧,我JDK定义一个标准(接口),其他的我不管,在要使用的时候我怎么知道要取哪个实现类呢?所以就需要定义一个规则,JDK通过这个规则可以找到该实现类进行加载。
- SPI英文全称是
- 为什么要Dubbo源码要从SPI开始说起?
- 因为在Dubbo中运用了大量的SPI机制,实现灵活的扩展,比如协议、负载均衡、序列化、代理的生成等等地方。所以接下来要去深入Dubbo的源码,SPI是必须要弄懂的,不然到时候你就不知道自己在哪里,然后为什么要这样。
0x02 java中的SPI
- 上面我们稍稍的讲了下SPI,可能大家伙还是很懵逼。所以接下来我们搞个demo,配合代码来看就好很多了。
- 在写demo之前,我们先说说要实现Java的SPI需要注意的点(规则)
- 新建一个接口(注意,是接口)
- 在
classpath:/META-INF/service
路径下新建一个以接口全类名为名的文件 - 在文件内部写上改接口的实现类,可以有多个,用换行分割,也就是一行一个实现类
- 文件的编码格式是UTF-8
- 通过ServiceLoader.load(接口.class)来进行发现和加载。
- 下面我们开始准备结合Demo来看看SPI到底是个什么鬼。
-
先定义一个接口。这里我们就简单来,就
IHelloService
了。代码如下:1
2
3
4
5
6
7
8
9
10/**
* @author zyzling
*/
public interface ISayHelloService {
/**
* say hello
* @return
*/
public String sayHello();
} -
接下来我们定义两个实现类,为了证明可以有多个。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
* @author :zyzling
*/
public class SayHelloServiceImpl implements ISayHelloService {
public String sayHello() {
return "hello welcome";
}
}
/**
* @author :zyzling
*/
public class SayHelloWorldServiceImpl implements ISayHelloService {
public String sayHello() {
return "hello world";
}
} -
最后我们按照约定,在
classpath:META-INF/service
目录下创建一个名为接口全限定名的文件,里面写好我们两个实现类的全限定名。路径如下:
- 内容如下:
1
2top.zyzling.provider.SayHelloServiceImpl
top.zyzling.provider.SayHelloWorldServiceImpl
- 内容如下:
-
最后的最后,我们在main方法中进行调用。代码如下:
1
2
3
4
5
6
7
8
9
10public class App{
public static void main( String[] args ){
//通过ServiceLoader来加载我们的接口,
ServiceLoader<ISayHelloService> serviceLoaders = ServiceLoader.load(ISayHelloService.class);
//遍历得到我们具体的实现类,然后调用方法
for (ISayHelloService service : serviceLoaders) {
System.out.println(service.sayHello());
}
}
} -
运行结果如下:
- 可见,是依次按照我们在文件中书写的顺序来的。那么我们下面开始往深里分析下它的实现吧。
-
ServiceLoader
源码分析- 我们先从
ServiceLoader.load()
方法入手。看看其做了什么。代码如下所示1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public static <S> ServiceLoader<S> load(Class<S> service) {
//通过线程获取当前的classloader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
//调用load的重载方法。方法在下面
return ServiceLoader.load(service, cl);
}
//load方法的重载
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){
//看上去就new了个ServiceLoader
return new ServiceLoader<>(service, loader);
}
//ServiceLoader的构造方法
private ServiceLoader(Class<S> svc, ClassLoader cl) {
//断言
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//classLoader处理
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
//jdk安全处理
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
//调用reload方法。代码如下
reload();
}
//重新加载
public void reload() {
//清空所有的provider。这个providers是一个LinkedHashMap。定义为:
//private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
providers.clear();
//lookupIterator 实现了Iterator接口,所以直接可以看做是一个迭代器。我们看下他的构造方法
//private class LazyIterator implements Iterator<S>
lookupIterator = new LazyIterator(service, loader);
}
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
} - 从上面的代码看,好像也没有做什么,只是给对象赋初值而已。最终是返回一个
LazyIterator
对象,那么我们的文件是什么时候解析的呢?类又是什么时候加载的呢?没错啦,就是遍历的时候进行加载的。因为我们使用时用的增强for,本质上也是在用迭代器,既然是用迭代器,也就是会调用ServiceLoader.iterator()
方法,那么就让我们看下吧。分别如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53public Iterator<S> iterator() {
return new Iterator<S>() {
//获取缓存的迭代器,因为我们new的时候会清空,所以这里肯定是空的。
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
//先判断缓存中有没有下一个,没有的话就到lookupIterator中找
if (knownProviders.hasNext())
return true;
//代码在下方
return lookupIterator.hasNext();
}
public S next() {
//如果缓存中,就直接返回了,不需要在去实例化了。
if (knownProviders.hasNext())
return knownProviders.next().getValue();
//如果缓存中没有,就需要实例化,代码如下
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
//------------------------ 以下代码是在lookupIterator中 -------------
public boolean hasNext() {
if (acc == null) {
//调用hasNextService()方法。
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
//调用nextService()方法
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
} - 那么依照上面的代码,主要逻辑是在
hasNextServie()
和nextService()
方法中。我们先看hasNextService()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90private boolean hasNextService() {
//nextName是一个属性,存放我们当前找到的实现类。首次进来肯定是=null的。所以继续往下走
//nextName的定义为:String nextName = null;
if (nextName != null) {
return true;
}
//configs是一个Enumeration<URL>。存放我们读到的资源路径。首次进来也是null。所以执行if体内的代码
if (configs == null) {
try {
//拼接路径。PREFIX = META-INF/services/。所以为什么我们的文件要放在这个路径下。
//service.getName()则是取接口的全限定名,所以为什么我们要以接口的全限定名为文件名。
String fullName = PREFIX + service.getName();
//loader就是Classloader。这个在上面new LazyIterator()的时候就已经赋值了
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
//加载资源
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
//pending是一个迭代器,定义是这样的:Iterator<String> pending = null;
//首次进来时肯定也是null。所以进while循环。
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
//调用parse解析文件。
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;
//names存放我们每个实现类
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
//读一行,调用parseLine()解析一行到names里面
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();
}
//解析
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
List<String> names)throws IOException, ServiceConfigurationError{
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}- 上面解析的代码我们就不一行一行分析了。总之就是读取配置文件,然后把我们实现类的全限定名加入到
names
这个list里面,最后返回一个迭代器。 - 总结下这个
hasNextService()
方法吧,如果之前解析过配置,则直接判断是否还有下一个实现类,如果有,就把实现类读到nextName中。如果是第一次进来,没有解析过配置,就会从META-INF/service/
目录下找以接口全限定名的文件去解析。然后把使用迭代器的next()
读取实现类赋值给nextName
。
- 上面解析的代码我们就不一行一行分析了。总之就是读取配置文件,然后把我们实现类的全限定名加入到
- 既然配置文件的解析是在
hasNext()
中,那么实例化一定是在next()
方法中,也就是nextService()
里面1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
//把nextName,也就是hasNextService中获取到的实现类的全限定名给cn
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//使用Class.forName来加载Class
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 {
//创建实例并且强转
S p = service.cast(c.newInstance());
//放到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
}
- 我们先从
- 上面我们已经把
ServiceLoader从创建->解析文件->实例化Class
已经分析完了。通过上面的使用+分析,我们可以发现java SPI有如下特点:- 通过Class获取的是一个迭代器,使用的时候需要迭代。(缺点,使用不方便)
- 类的实例化是在使用的时候进行的,也就是说会延迟new一个对象。(优点,延迟实例化对象)
- 每次使用时,都会把配置文件中所有的类进行实例化,不管你后续是否要用到它。(缺点,不使用也创建对象,浪费内存)
- 下面我们来说说Dubbo中的SPI机制。
0x03 Dubbo中的SPI
- Dubbo实现了自己的一套SPI扩展,他的规则如下:
- 定义一个接口,在接口上加上
@SPI
注解 - 在
classpath:META-INF/dubbo
或者classpath:META-INF/dubbo/internal
或者classpath:META-INF/services
目录下,创建一个文件名为接口的全限定名的文件 - 在文件中定义
key=value
形式的字符串。其中key用来在程序中取对应的实现类。value为实现类的全限定名。类似如下:1
2helloService=top.zyzling.HelloServiceImpl
helloWorldService=top.zyzling.HelloWorldServiceImpl
- 定义一个接口,在接口上加上
- 上面说了规则,那我们现在就来实战一波吧。
- 首先定义一个接口
IHelloService
,在接口上加上@SPI
注解1
2
3
4
5
6
7
8
9
10package top.zyzling.service;
import org.apache.dubbo.common.extension.SPI;
public interface IHelloService {
/**
* hello
* @return
*/
String hello();
} - 在
classpath:META-INF/dubbo
目录下创建一个名为top.zyzling.service.IHelloService
的文件。如图:
- 创建两个实现类,
HelloServiceImpl
和HelloServiceProvider
。分别如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package top.zyzling.impl;
public class HelloServiceImpl implements IHelloService {
public String hello() {
return "[HelloServiceImpl] hello dubbo SPI";
}
}
package top.zyzling.impl;
public class HelloServiceProvider implements IHelloService {
public String hello() {
return "[HelloServiceProvider] hello dubbo SPI";
}
} - 在文件中以key=value的形式来声明我们的实现类。如下:
1
2helloServiceImpl = top.zyzling.impl.HelloServiceImpl
helloServiceProvider = top.zyzling.impl.HelloServiceProvider - ok,规则就是上面那样,那么我们来在main方法中使用吧。
1
2
3
4
5
6
7
8
9
10
11
12
13public class App {
public static void main( String[] args ){
//通过接口获取我们的ExtensionLoader
//可以类比于我们的ClassLoader,所有的扩展点(接口的实现类)都是由它进行加载
ExtensionLoader<IHelloService> extensionLoader = ExtensionLoader.getExtensionLoader(IHelloService.class);
//获取扩展点,参数是我们在文件中定义的key
IHelloService helloServiceImpl = extensionLoader.getExtension("helloServiceImpl");
IHelloService helloServiceProvider = extensionLoader.getExtension("helloServiceProvider");
//调用方法,输出
System.out.println(helloServiceImpl.hello());
System.out.println(helloServiceProvider.hello());
}
} - 结果如下:
- 首先定义一个接口
- 上面我们介绍了dubbo扩展点的一种获取方式,也就是直接使用
extensionLoader.getExtension(key)
的方式直接获取key所对应的实现类。那么我有一些扩展点我不想他在框架初始化的时候进行实例化,我想在调用接口中的方法时,通过参数来自动选择需要实例化的类,也就是我要先调用该接口的方法,然后在方法中根据参数实例化扩展类,这种情况该怎么办?没错,就是用代理方式解决,我先搞个代理类,在执行方法的时候,在代理类中通过参数,动态的获取真正的扩展类,然后再进行调用。而在dubbo中,这一切都交给了一个@Adaptive
注解搞定。那么我们下面就来说说这个。- 首先说下它的规则。
- 还是和上面一样,需要建一个接口,然后在接口上加上
@SPI
注解 @Adaptive
可以加在实现类上,也可以加在接口的方法声明。那么这里有什么不同呢?- 加在实现类上(Dubbo中不常见):
- 代表这个类就是我们的代理类,也就是代理类是需要我们手动定义,在这个类里会进行动态的获取真正的扩展类.
- 加在接口方法声明上(Dubbo中很常见):
- 代表这个代理类是dubbo使用字节码技术帮我们在运行时自动生成.
- 代理类的名称是
接口名$Adaptive
,比如Protocal$Adaptive
- 加在实现类上(Dubbo中不常见):
- 获取我们的扩展类时分两步:
- 先通过
ExtensionLoader.getExtensionLoader(接口.class).getAdaptiveExtension()
获取我们的代理类,不管这个代理类是自动生成的还是我们定义好的。 - 再调用代理类的方法,在里面动态获取我们真正的扩展类。在代理类中,也是通过
ExtensionLoader#getExtesion(String)
来获取真正的扩展类
- 先通过
- 还是和上面一样,需要建一个接口,然后在接口上加上
- 规则清楚了,按照惯例,先写个demo出来(
@Adaptive
加在实现类上)。- 按照@SPI的规则,我们需要先定义一个接口,简单起见,和上面保持一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package top.zyzling.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.SPI;
/**
* @author :zyzling
*/
public interface IHelloService {
/**
* hello
* @param url
* @return
*/
String sayHello(URL url);
} - 然后定义我们的两个实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14package top.zyzling.imp;
public class HelloSeriviceImpl implements IHelloService {
public String sayHello(URL url) {
return "[HelloServiceImpl] url:"+url;
}
}
//----------------------------------
public class HelloServiceProvider implements IHelloService {
public String sayHello(URL url) {
return "[HelloServiceProvider] url:"+url;
}
} - 定义我们的代理类,并在代理类上加上
@Adaptive
注解1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package top.zyzling.imp;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.ExtensionLoader;
import top.zyzling.service.IHelloService;
//@Adaptive方法,标识该类为代理类
public class HelloServiceAdaptive implements IHelloService {
public String sayHello(URL url) {
System.out.println("[HelloServiceAdaptive] url:"+url);
String type = url.getParameter("type");
//根据Type的不同获取不同的实现类
ExtensionLoader<IHelloService> loader = ExtensionLoader.getExtensionLoader(IHelloService.class);
IHelloService extension = loader.getExtension(type);
return extension.sayHello(url);
}
} - 最后,在
classpath:META-INF/dubbo
目录下创建一个名为top.zyzling.service.IHelloService
的文件,并在里面定义好key和value1
2
3helloServieAdaptive = top.zyzling.imp.HelloServiceAdaptive
helloServiceImpl = top.zyzling.imp.HelloSeriviceImpl
helloServiceProvider = top.zyzling.imp.HelloServiceProvider - Main方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class App {
public static void main( String[] args ){
ExtensionLoader<IHelloService> loader = ExtensionLoader.getExtensionLoader(IHelloService.class);
//这时候获取的是一个代理类。可能是我们自己定义的,也有可能是动态生成的,
//这个需要看@Adaptive注解加在哪。
IHelloService adaptiveExtension = loader.getAdaptiveExtension();
//构造一个URL对象,注意是dubbo包下面的。信息都是随便填
URL implUrl = new URL("http","localhost",8080);
implUrl = implUrl.addParameter("type","helloServiceImpl");
System.out.println(adaptiveExtension.sayHello(implUrl));
System.out.println("-------------------------------");
URL providerUrl = new URL("http","localhost",8089);
providerUrl = providerUrl.addParameter("type","helloServiceProvider");
System.out.println(adaptiveExtension.sayHello(providerUrl));
}
} - 运行结果:
- 按照@SPI的规则,我们需要先定义一个接口,简单起见,和上面保持一致。
- 首先说下它的规则。
- 除了上面这种自适应扩展,dubbo中还提供了一种激活扩展,也就是指定条件,只有满足了该条件,类实例才会被加载。dubbo中使用
@Activate
注解来做这事。- 规则:
- 和前面一样,需要在接口上加上
@SPI
注解,并且也需要在对应目录创建以接口全限定名为文件名的文件,里面定义好key和value。 - 在实现类上加上
@Activate
注解,并且通过value属性来指定条件,注意,该条件是一个key,当URL的参数中有该Key时,激活当前扩展。@Activate
还有其他的属性。分别如下:- group:可以通过group过滤,需要配合
ExtensionLoader#getActivateExtension(URL, String, String)
使用。只有当传递过来的group在配置的group中,才会激活。 - value:当指定的键出现在URL的参数中时,激活当前扩展名。
- before:配置相对位置。也就是可以通过这个来配置实现类的顺序
- after:同上,也是和顺序相关
- order:配置绝对位置,同上,也是和顺序相关
- group:可以通过group过滤,需要配合
- 那么怎么获取激活扩展点呢?可以通过
Extension#getActivateExtension(URL,String)
来获取。其中String类型的参数代表着key。也就是说,如果我有一个扩展类的@Activate
的value为xxx
,那么我调用getActivateExtension
的时候,传递xxx
,就会获取相应的类。
- 和前面一样,需要在接口上加上
- demo:
- 定义个一个接口,加上
@SPI
注解1
2
3
4
5
6
7
8
9
10
11
12/**
* @author :zyzling
*/
public interface IHelloService {
/**
* hello
* @param url
* @return
*/
String sayHello(URL url);
} - 定义连个实现类,在上面加上
@Activate
注解,一个value属性值为impl
,一个为provider
.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
* @author :zyzling
*/
"provodier") (value=
public class HelloServiceProvider implements IHelloService {
public String sayHello(URL url) {
return "[HelloServiceProvider] activateExtension url:"+url;
}
}
/**
* @author :zyzling
*/
"impl") (value=
public class HelloServiceImpl implements IHelloService {
public String sayHello(URL url) {
return "[HelloServiceImpl] activateExtension url:"+url;
}
} - 在对应的目录下创建一个以接口全限定名为文件名的文件,里面的内容如下:
1
2helloServiceProvider = top.zyzling.impl.HelloServiceProvider
helloServiceImpl = top.zyzling.impl.HelloServiceImpl - main方法
1
2
3
4
5
6
7
8
9
10
11
12
13public class App {
public static void main( String[] args ){
ExtensionLoader<IHelloService> extensionLoader = ExtensionLoader.getExtensionLoader(IHelloService.class);
URL url = new URL("http","localhost",8080);
//key为@Activate的value属性,而value就是配置文件中你需要获取的实现类对应的key.多个可以用“,”分割
url = url.addParameter("impl","helloServiceImpl,helloServiceProvider");
//这里的第二个参数传你需要进行激活的key。也就是说,在url中要有该key,才会创建类的实例
List<IHelloService> impl = extensionLoader.getActivateExtension(url, "impl");
for (IHelloService iHelloService : impl) {
System.out.println(iHelloService.sayHello(url));
}
}
} - 运行结果
- 定义个一个接口,加上
- 规则:
0x04 总结
这篇我们从Jdk的SPI一路说到Dubbo的SPI,也通过一些例子来演示了一下,想必到这里,你应该对SPI有了一定的了解,那么SPI用一句话来说就是:内部对外提供接口,由外部去做具体实现。内部通过定义好的一些规则来对外部实现进行加载。整一系列就是Service Provider Interface
。
对于dubbo的SPI来说,其实他就分为3种,一种是普遍的通过Key来找对应的实现类(静态扩展点)。一种是提供一个代理类,由代理类利用第一种通过Key来获取对应的实现类,在调用对应的方法(自适应扩展点)。最后一种是通过条件来判断是否需要加载(激活扩展点)。相信大家通过上面的例子,对这三种都有了一定的了解,那么下篇我们就带来Dubbo SPI的源码分析,去了解它们内部的秘密。
知识浅薄,如有表达不当之处,还望指出。谢谢观看,enjoy~
评论