banner
NEWS LETTER

dubbo的spi

Scroll down

dubbo的spi相比于jdk的spi而言,提供了更为强大的功能,主要来说是帮助我们更好的面对多个服务互相依赖的场景,并且做了一定优化。(例如:按需加载)

按需加载

jdk的spi的配置文件

1
2
org.example.ToyotaCar
org.example.HondaCar

dubbo的spi的配置文件

1
2
3
4
5
6
7
toyota=org.example.ToyotaCar
honda=org.example.HondaCar
wrapper=org.example.aop.CarWrapper1
wrapper=org.example.aop.CarWrapper2
Race=org.example.ioc.RaceRes
red=org.example.ioc.PenRes

二者的区别在于dubbo中使用键值,可以实现按需加载,请注意,该加载并非指配置文件的加载,而加载配置文件之后的对于服务对象的实例化

示例

jdk的spi

1
2
3
4
5
6
7
8
9
      ServiceLoader<Car> load = ServiceLoader.load(Car.class);

// 获取迭代器遍历
Iterator<Car> iterator = load.iterator();
while (iterator.hasNext()){
Car registry = iterator.next();
registry.run();
}

追溯源码可以看到迭代器内部。

image-20250122174648892

关键成员变量

Iterator pending ;//配置文件读取的数据 该变量也是迭代器
String nextName ;//下一个将要读取的配置

存在关键方法

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
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
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:

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
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 {
S p = service.cast(c.newInstance());//实例化对象
providers.put(cn, p);//延迟加载,将其收入到一个map集合当中
return p;//返回加载元素
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}

dubbo的spi

dubbo的spi则使用键值加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ExtensionLoader<Car> extensionLoader;
@Before
public void fi(){
extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
}


/**
* @author:wenzhuo4657
des:
dubbo基本服务发现,和jdk的主要区别在于可以用键去获取指定的扩展实现。
*/
@Test
public void test(){
Car car = extensionLoader.getExtension("ali",false);//false表示不进行自动装配等其他配置,默认为true,
car.run();
if (car instanceof HondaCar){
System.out.println("true");
}
}

追溯getExtension方法,可以看到关键实例化对象的createExtension方法

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
private T createExtension(String name, boolean wrap) {
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);
}
injectExtension(instance);


if (wrap) {

List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}

if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
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);
}
}

1,提前获取类加载器,

Class clazz = getExtensionClasses().get(name);方法内部可以看到有一个成员变量`private final Holder>> cachedClasses = new Holder<>();`

DUBBO的扩展点加载

扩展点加载 | Apache Dubbo:该文档旨在对官网翻译,助于理解

dubbo扩展点加载与单纯的spi不同,会使用Wrapper包装类对服务方法进行扩展,实际上将,这有点像aop。

包装之后,获取的对象将不是所需要的服务对象,而是包装类对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class XxxProtocolWrapper implements Protocol {
Protocol impl;

public XxxProtocolWrapper(Protocol protocol) { impl = protocol; }

// 接口方法做一个操作后,再调用extension的方法
public void refer() {
//... 一些操作
impl.refer();
// ... 一些操作
}

// ...
}

可以在加载是通过参数指定是否开启包装类代理。

1
Car car = extensionLoader.getExtension("ali",false);//false表示不进行自动装配等其他配置,默认为true,

自动装配

自动装配对应spring DI自动注入,判断方式为方法名称。

形如setWheelMaker 方法的 WheelMaker 也是扩展点则会注入 WheelMaker 的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RaceCarMaker implements CarMaker {
WheelMaker wheelMaker;

public void setWheelMaker(WheelMaker wheelMaker) {
this.wheelMaker = wheelMaker;
}

public Car makeCar() {
// ...
Wheel wheel = wheelMaker.makeWheel();
// ...
return new RaceCar(wheel, ...);
}
}

但是这种注入无法指定依赖服务的实现,一般为默认实现或者优先级判断使用哪个实现,但无论是哪种都是全局唯一的默认,无法实现特例。

自适应装配

该装配利用了包装类和方法参数,实现在扩展时加载服务对象并调用方法。

该方式和自动装配并不冲突,只是并不使用自动装配的服务,并且由于包装类方法执行的缘故,甚至不用判断是否使用默认服务。

1
2
3
4
5
6
7
8
9
public interface CarMaker {
@Adaptive({"CarMaker"})
Car makeCar(URL url);
}

public interface WheelMaker {
@Adaptive({"WheelMaker"})
Wheel makeWheel(URL url);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RaceCarMaker implements CarMaker {
WheelMaker wheelMaker;

public void setWheelMaker(WheelMaker wheelMaker) {
this.wheelMaker = wheelMaker;
}

public Car makeCar(URL url) {
// ...
Wheel wheel = wheelMaker.makeWheel(url);
// ...
return new RaceCar(wheel, ...);
}
}
1
2
3
4
5
6
7
Pen pen = extensionLoader.getAdaptiveExtension();
URL url = new URL("dubbo", "127.0.0.1", 20880).
addParameter("WheelMaker", "wheel").
addParameter("CarMaker","car");

// 调用方法
pen.run(url);

使用@Adaptive注解定义包装类实例化服务的key,在url参数中指定,最终实现特定服务调用特定服务。

深入源码中即可发现这一实现的依赖于包装类。

1
2
3
public java.lang.String queryCountry(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("person.service", "china");//获取指定扩展服务         if (extName == null)             throw new IllegalStateException("Failed to get extension (org.dubbo.spi.example.PersonService) name from url (" + url.toString() + ") use keys([person.service])");         org.dubbo.spi.example.PersonService extension = (org.dubbo.spi.example.PersonService) ExtensionLoader.getExtensionLoader(org.dubbo.spi.example.PersonService.class).getExtension(extName); //扩展服务加载      
return extension.queryCountry(arg0);//扩展服务执行    
}

它会在扩展执行时通过url参数中的指定值,加载扩展服务执行。 

补充要点

jdk的加载默认使用无参构造器

这一点可以在ServiceLoader.LazyIterator#nextService()中看到

S p = service.cast(c.newInstance());

而Dubbo可以通过url携带参数实例化。

其他文章
cover
零拷贝
  • 25/02/15
  • 16:23