1.4Dubbo的SPI机制
作者:互联网
文章目录
- 预览效果
- 自定义
- dubbo的Aop
- dubbo的IOC
- ExtensionLoader
- getExtension(String name)
- createExtension(String name)
- getExtensionClasses
- loadResource⽅法
- loadClass⽅法
- Dubbo中的IOC
- Dubbo中的AOP
- createAdaptiveExtensionClass⽅法
- Car接口的接⼝的Adaptive类
- Activate扩展点
- @Activate
预览效果
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol dubbo = extensionLoader.getExtension("dubbo");
System.out.println(dubbo);
自定义
接口
//@SPI("red") 未配置默认使用red
@SPI
public interface Car {
String getCarColor();
}
实现类
public class BlackCar implements Car{
@Override
public String getCarColor() {
return "black";
}
}
public class RedCar implements Car{
@Override
public String getCarColor() {
return "red";
}
}
配置
1.在classpath下创建META-INF目录
2.在META-INF下创建dubbo目录
3.在dubbo下创建接口全类名文件com.zhuang.Car
4.在com.zhuang.Car文件里边编写实现类全类名
red=com.zhuang.RedCar
black=com.zhuang.BlackCar
使用
ExtensionLoader<Car> carSpi = ExtensionLoader.getExtensionLoader(Car.class);
Car red = carSpi.getExtension("red");
System.out.println(red);
dubbo的Aop
编写wrapper类
//实现Car接口,提供构造方法
public class CarWrapper implements Car{
Car car;
public CarWrapper(Car car){
this.car = car;
}
@Override
public String getCarColor() {
System.out.println("CarWrapper");
return car.getCarColor();
}
}
添加配置
在com.zhuang.Car文件中最后添加
com.zhuang.CarWrapper
使用
ExtensionLoader<Car> carSpi = ExtensionLoader.getExtensionLoader(Car.class);
Car red = carSpi.getExtension("red");
System.out.println(red);
dubbo的IOC
改造Car接口
@SPI
public interface Car {
@Adaptive
String getCarColor(URL url);
}
改造实现类
//org.apache.dubbo.common.URL
public class BlackCar implements Car{
@Override
public String getCarColor(URL url) {
return "black";
}
}
public class RedCar implements Car{
@Override
public String getCarColor(URL url) {
return "red";
}
}
编写Person接口
@SPI
public interface Person {
Car getCar();
}
编写Person接口实现类
public class NormalPerson implements Person {
private Car car;
public void setCar(Car car) {
this.car = car;
}
@Override
public Car getCar() {
return car;
}
}
添加Person的SPI配置文件
运行
public static void main(String[] args) {
ExtensionLoader<Person> carSpi = ExtensionLoader.getExtensionLoader(Person.class);
Person person = carSpi.getExtension("normal");
URL url = new URL("x", "localhost", 8080);
url = url.addParameter("car", "black");
System.out.println(person.getCar().getCarColor(url));
}
ExtensionLoader
ExtensionLoader表示某个接⼝的扩展点加载器,可以⽤来加载某个扩展点实例。
在ExtensionLoader中除开有上⽂的static的Map外,还有两个⾮常重要的属性:
1. Class<?> type:表示当前ExtensionLoader实例是哪个接⼝的扩展点加载器
2. ExtensionFactory objectFactory:扩展点⼯⼚(对象⼯⼚),可以获得某个对象
ExtensionLoader和ExtensionFactory的区别在于:
1. ExtensionLoader最终所得到的对象是Dubbo SPI机制产⽣的
2. ExtensionFactory最终所得到的对象可能是Dubbo SPI机制所产⽣的,也可能是从Spring容器中所获得的对象
ExtensionLoader中有三个常⽤的⽅法:
1. getExtension("dubbo"):表示获取名字为dubbo的扩展点实例
2. getAdaptiveExtension():表示获取⼀个⾃适应的扩展点实例
3. getActivateExtension(URL url, String[] values, String group):表示⼀个可以被url激活的扩展点实例
getExtension(String name)
在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。
createExtension(String name)
在调⽤createExtension(Stringname)⽅法去创建⼀个扩展点实例时,要经过以下⼏个步骤:
1. 根据name找到对应的扩展点实现类
2. 根据实现类⽣成⼀个实例,把实现类和对应⽣成的实例进⾏缓存
3. 对⽣成出来的实例进⾏依赖注⼊(给实例的属性进⾏赋值)
4. 对依赖注⼊后的实例进⾏AOP(Wrapper),把当前接⼝类的所有的Wrapper全部⼀层⼀层包裹在实例
对象上,没包裹个Wrapper后,也会对Wrapper对象进⾏依赖注⼊
5. 返回最终的Wrapper对象
getExtensionClasses
getExtensionClasses()是⽤来加载当前接⼝所有的扩展点实现类的,返回⼀个Map。之后可以从这个
Map中按照指定的name获取对应的扩展点实现类。
当把当前接⼝的所有扩展点实现类都加载出来后也会进⾏缓存,下次需要加载时直接拿缓存中的。
Dubbo在加载⼀个接⼝的扩展点时,思路是这样的:
1. 根据接⼝的全限定名去META-INF/dubbo/internal/⽬录下寻找对应的⽂件,调⽤loadResource⽅法
进⾏加载
2. 根据接⼝的全限定名去META-INF/dubbo/⽬录下寻找对应的⽂件,调⽤loadResource⽅法进⾏加载
3. 根据接⼝的全限定名去META-INF/services/⽬录下寻找对应的⽂件,调⽤loadResource⽅法进⾏加
载
这⾥其实会设计到⽼版本兼容的逻辑
loadResource⽅法
loadResource⽅法就是完成对⽂件内容的解析,按⾏进⾏解析,会解析出"="两边的内容,"="左边的内容就是扩展点的name,右边的内容就是扩展点实现类,并且会利⽤ExtensionLoader类的类加载器来加载扩展点实现类。
然后调⽤loadClass⽅法对name和扩展点实例进⾏详细的解析,并且最终把他们放到Map中去。
loadClass⽅法
loadClass⽅法会做如下⼏件事情:
1. 当前扩展点实现类上是否存在@Adaptive注解,如果存在则把该类认为是当前接⼝的默认⾃适应类(接
⼝代理类),并把该类存到cachedAdaptiveClass属性上。
2. 当前扩展点实现是否是⼀个当前接⼝的⼀个Wrapper类,如果判断的?就是看当前类中是否存在⼀个构造⽅法,该构造⽅法只有⼀个参数,参数类型为接⼝类型,如果存在这⼀的构造⽅法,那么这个类就是该接⼝的Wrapper类,如果是,则把该类添加到cachedWrapperClasses中去,cachedWrapperClasses是⼀个set。
3. 如果不是⾃适应类,或者也不是Wrapper类,则判断是有存在name,如果没有name,则报错。
4. 如果有多个name,则判断⼀下当前扩展点实现类上是否存在@Activate注解,如果存在,则把该类添加到cachedActivates中,cachedWrapperClasses是⼀个map。
5. 最后,遍历多个name,把每个name和对应的实现类存到extensionClasses中去,extensionClasses就是上⽂所提到的map。
6.⾄此,加载类就⾛完了。
7.回到createExtension(Stringname)⽅法中的逻辑,当前这个接⼝的所有扩展点实现类都扫描完了之后,就可以根据⽤户所指定的名字,找到对应的实现类了,然后进⾏实例化,然后进⾏IOC(依赖注⼊)和AOP。
Dubbo中的IOC
1. 根据当前实例的类,找到这个类中的setter⽅法,进⾏依赖注⼊
2. 先分析出setter⽅法的参数类型pt
3. 在截取出setter⽅法所对应的属性名property
4. 调⽤objectFactory.getExtension(pt,property)得到⼀个对象,这⾥就会从Spring容器或通过DubboSpi机制得到⼀个对象,⽐较特殊的是,如果是通过DubboSpi机制得到的对象,是pt这个类型的⼀个⾃适应对象(代理对象)。
5. 再反射调⽤setter⽅法进⾏注⼊
Dubbo中的AOP
dubbo中也实现了⼀套⾮常简单的AOP,就是利⽤Wrapper,如果⼀个接⼝的扩展点中包含了多个Wrapper类,那么在实例化完某个扩展点后,就会利⽤这些Wrapper类对这个实例进⾏包裹。
createAdaptiveExtensionClass⽅法
createAdaptiveExtensionClass⽅法就是Dubbo中默认⽣成Adaptive类实例的逻辑。说⽩了,这个实例就是当前这个接⼝的⼀个代理对象
Car接口的接⼝的Adaptive类
package com.zhuang;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Car$Adaptive implements com.zhuang.Car {
public java.lang.String getCarColor(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("car");
if(extName == null) throw new IllegalStateException("Failed to get extension (com.zhuang.Car) name from url (" + url.toString() + ") use keys([car])");
com.zhuang.Car extension = (com.zhuang.Car)ExtensionLoader.getExtensionLoader(com.zhuang.Car.class).getExtension(extName);
return extension.getCarColor(arg0);
}
public void testAdaptive() {
throw new UnsupportedOperationException("The method public abstract void com.zhuang.Car.testAdaptive() of interface com.zhuang.Car is not adaptive method!");
}
}
说明
可以看到,Car接⼝中有两个个⽅法,但是只有getCarColor⽅法进⾏代理。
因为Car接⼝中在getCarColor⽅法上加了@Adaptive注解。但是,不是只要在⽅法上加了
@Adaptive注解就可以进⾏代理,还有其他条件,⽐如:
1. 该⽅法如果是⽆参的,那么则会报错
2. 该⽅法有参数,可以有多个,并且其中某个参数类型是URL,那么则可以进⾏代理
3. 该⽅法有参数,可以有多个,但是没有URL类型的参数,那么则不能进⾏代理
4. 该⽅法有参数,可以有多个,没有URL类型的参数,但是如果这些参数类型,对应的类中存在getUrl⽅法(返回值类型为URL),那么也可以进⾏代理
Activate扩展点
上⽂说到,每个扩展点都有⼀个name,通过这个name可以获得该name对应的扩展点实例,但是有的场景下,希望⼀次性获得多个扩展点实例
代码
ExtensionLoader<Filter> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
URL url = new URL("http://", "localhost", 8080);
url = url.addParameter("cache", "test");
List<Filter> activateExtensions = extensionLoader.getActivateExtension(url,new String[]{"validation"}, CommonConstants.CONSUMER);
for (Filter activateExtension : activateExtensions) {
System.out.println(activateExtension);
}
//结果
org.apache.dubbo.rpc.filter.ConsumerContextFilter@5e853265
org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter@67205a84
org.apache.dubbo.monitor.support.MonitorFilter@7d0587f1
org.apache.dubbo.cache.filter.CacheFilter@5d76b067
org.apache.dubbo.validation.filter.ValidationFilter@2a17b7b6
说明
前三个是通过CommonConstants.CONSUMER找到的
CacheFilter是通过url中的参数找到的
ValidationFilter是通过指定的name找到的
@Activate
在⼀个扩展点类上,可以添加@Activate注解,这个注解的属性有:
1. String[] group():表示这个扩展点是属于哪组的,这⾥组通常分为PROVIDER和CONSUMER,表示该扩展点能在服务提供者端,或者消费端使⽤
2. String[] value():表示的是URL中的某个参数key,当利⽤getActivateExtension⽅法来寻找扩展点时,如果传⼊的url中包含的参数的所有key中,包括了当前扩展点中的value值,那么则表示当前url可以使⽤该扩展点。
标签:1.4,Dubbo,dubbo,url,Car,扩展,SPI,ExtensionLoader,public 来源: https://blog.csdn.net/weixin_50042304/article/details/113761772