目录
1、Dubbo核心模块职责介绍
Dubbo发展到现在,已经有3.0版本了,但是官方建议应用于生产的最高版本还是2.7,所以我们以下课程都是以2.7作为标准!
源码下载地址:https://github.com/apache/dubbo/tree/2.7.x;该源码项目是完全基于MAVEN开发的,下载解压后直接导入idea即可,下载相关依赖包会比较久
dubbo common 通用模块,定义了几乎所有dubbo模块都会使用到的一些通用与业务领域无关的工具类(IO处理、日志处理、配置处理、类处理等等),线程池扩展、二进制代码处理、class编译处理、JSON处理、数据存储接口,系统版本号等等通用的类和接口。 dubbo rpc 分布式服务框架的核心是RPC,这是最基本的功能,这个模块定义了rpc的一些抽象的rpc接口和实现类,包括服务发布,服务调用代理,远程调用结果及异常,RPC调用网络协议,RPC调用监听器和过滤器等等。该模块提供了默认的基于dubbo协议的实现模块,还提供了hessian、http、rest、rmi、thrift和webservice等协议的实现,还实现了injvm的本地调用实现,灵活性强,非常通用,能够满足绝大多数项目的使用需求,而且还可以自行实现RPC协议。 dubbo registry 注册中心也是最重要的组成部分,它是RPC中的consumer和provider两个重要角色的协调者。该项目定义了核心的注册中心接口和实现。具体实现留给了其它项目。有一个默认的实现模块,注册中心提供了mutilcast、redis和zookeeper等多种方式的注册中心实现,用于不同的使用场景。 dubbo remoting 该模块是dubbo中的远程通讯模块。RPC的实现基础就是远程通讯,consmer要调用provider的远程方法必须通过网络远程通讯实现。该模块定义了远程传输器、终端(endpoint)、客户端、服务端、编码解码器、数据交换、缓冲区、通讯异常定义等等核心的接口及类构成。他是对于远程网络通讯的抽象。提供了诸如netty、mina、grizzly、http、p2p和zookeeper的协议和技术框架的实现方式。 dubbo monitor 该模块是dubbo的监控模块,通过该模块可以监控服务调用的各种信息,例如调用耗时、调用量、调用结果等等,监控中心在调用过程中收集调用的信息,发送到监控服务,在监控服务中可以存储这些信息,对这些数据进行统计分析,最终可以产生各种维护的调用监控信息。dubbo默认提供了一个实现,该实现非常简单,只是作为默认的实现范例,生产环境使用价值不高,需要自行实现自己的监控。 dubbo container dubbo服务运行容器api模块。定义了启动容器列表的包含应用程序入口main方法的类Main;定义了容器接口Container,该接口包含了启动和停止方法定义;还有一些通用的分页功能的相关类。dubbo内置了javaconfig、jetty、log4j、logback和spring几种容器的实现。 dubbo config 该模块依赖了几乎所有的其它模块,他是dubbo的配置模块,通过它的配置和组装将dubbo组件的多个模块整合在一起给最终的开发者提供有价值的分布式服务框架。通过它的配置可以让开发者选择符合自己需求和使用场景的模块和技术,它定义了面向dubbo使用者的各种信息配置,比如服务发布配置、方法发布配置、服务消费配置、应用程序配置、注册中心配置、协议配置、监控配置等等。另外还有一个spring的配置模块,定义了一些spring的XML Schema,能够大大简化使用dubbo的配置,可以大大降低spring使用场景的学习和配置成本。 dubbo cluster 该模块是dubbo实现的集群模块。支持远程服务的集群,支持多种集群调用策略,包括failover,failsafe,failfast,failback,forking等。并且支持目录服务,注册中心就是目录服务的一种实现,支持负载均衡,该模块还实现了路由器特性,此外还包括合并技术,当将调用请求分发给所有的服务提供者,则会返回多个结果,则将多个结果合并需要用到合并器的实现,该模块也是非常有个性的一个模块。 dubbo admin 该项目是一个web应用,可以独立部署,它可以管理dubbo服务,通过该管理应用可以连接注册中心,重点是读取注册中心中的信息,也可以通过该应用改写注册中心的信息,从而实现动态的管控服务。该模块的功能也非常简单,对于实际的生产使用场景,还需要对该应用的功能进行扩展和定制,以满足实际的使用场景。
2、源码基础
我们需要先学习几个基本的技能点,后续解读源码时才能事半功倍游刃有余,
2.1 SPI自适应
2.1.1 SPI简介
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。
2.1.2 Java SPI实例
package com.ydt.java.spi;
/**
* 汽车接口类
*/
public interface Car {
void run();
}
package com.ydt.java.spi;
/**
* 汽车接口实现类:奥迪
*/
public class AudiCar implements Car{
public void run() {
System.out.println("我是奥迪.....,精英专用!!!");
}
}
package com.ydt.java.spi;
/**
* 汽车接口实现类:宝马
*/
public class BMWCar implements Car{
public void run() {
System.out.println("我是宝马.....,暴发户专用!!!");
}
}
接下来 resources/META-INF/services 文件夹下创建一个文件,名称为Car接口的全限定名 com.ydt.java.spi.Car。文件内容为Car实现类的全限定的类名,如下:
//测试代码
@Test
public void test1(){
ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class);
Iterator<Car> iterator = serviceLoader.iterator();
while (iterator.hasNext()){
Car car = iterator.next();
if("com.ydt.java.spi.BMWCar".equals(car.getClass().getName())){
car.run();
}
}
}
2.1.3 Dubbo SPI实例
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader(扩展点加载器) 类中,通过 ExtensionLoader,我们可以加载指定的实现类。
与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Car接口上标注 @SPI 注解
@Test
public void test2(){
ExtensionLoader<Car> extensionLoader =
ExtensionLoader.getExtensionLoader(Car.class);
Car car = extensionLoader.getExtension("bmw");//指定加载
car.run();
}
2.1.4 Java SPI vs Dubbo SPI
首先很明显的一点:Dubbo SPI支持按需加载接口实现类,节省了资源开支,并且不要像Java SPI那样使用实现类的限定名来判断的方式(不觉得很不优雅吗?)
其次还增加了 IOC 和 AOP 等特性,Spring拥有IOC和AOP的特性,而不能说IOC和AOP只有Spring才拥有,他们两个只是一个概念而已:
2.1.4.1 Dubbo SPI实现IOC
使用Dubbo SPI实现IOC,需要引入一个URL(服务总线)的概念,其中包括我们后面要用到的很多关于Dubbo应用配置,如协议,端口,参数配置等!
而我们的Dubbo源码里面一个重要的地方就是围绕URL服务总线进行解析和应用!
1、修改之前Dubbo SPI实例,在Car接口run()方法中增加URL参数,并且添加@Adaptive注解。它的实现类也相应的增加该参数
package com.ydt.java.spi;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
/**
* 汽车接口类
* @SPI 可以设置默认值,如果使用默认值那么在按需加载的地方传参为"true"即可
*/
@SPI
public interface Car {
//增加汽车类型配置,用于决定使用哪个实现类加载,@Adaptive注解作用可以参照源码
@Adaptive(value = "carType")
void run(URL url);
}
2、增加一个司机类用于注入汽车类
package com.ydt.java.spi;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.SPI;
@SPI("old")
public interface Driver {
public void driverCar(URL url);
}
package com.ydt.java.spi;
import org.apache.dubbo.common.URL;
public class OldDriver implements Driver {
private Car car;
public void setCar(Car car) {
this.car = car;
}
public void driverCar(URL url){
System.out.println("哥哥考了驾照十年了......");
car.run(url);
}
}
配置SPI加载映射:
测试调用:
@Test
public void test3(){
ExtensionLoader<Driver> extensionLoader =
ExtensionLoader.getExtensionLoader(Driver.class);
Driver driver = extensionLoader.getDefaultExtension();
Map<String,String> map = new HashMap<>();
map.put("carType","bmw");
URL url = new URL(null,null,0,map);
driver.driverCar(url);
}
}
2.1.4.1 Dubbo SPI实现AOP
在之前的Dubbo SPI实例的基础上,增加一个汽车包装类,在run方法前后执行一些业务逻辑而对于用户来说是透明无感知的!
package com.ydt.java.spi;
public class CarWrapper implements Car {
private Car car;
public CarWrapper(Car car) {
this.car = car;
}
@Override
public void run() {
System.out.println("哥哥存钱喽.........");
car.run();
System.out.println("哥哥买车喽.........");
}
}
然后按Java SPI的配置方式配置包装类全限定名
继续测试:
对于SPI配置红色告警,那是因为现在Dubbo推荐使用的目录为resources/META-INF/dubbo/internal
2.1.5 Dubbo SPI源码解析
根据上面的案例,我们知道首先通过 ExtensionLoader的getExtensionLoader方法获取一个ExtensionLoader实例,然后再通过ExtensionLoader的getExtension 方法获取拓展类对象
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
//首先检查缓存
Object instance = holder.get();
//典型的单例双重校验锁
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
//缓存未命中则创建拓展对象
instance = createExtension(name);
//放入缓存
holder.set(instance);
}
}
}
return (T) instance;
}
创建拓展对象过程
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);
}
//向实例中注入依赖,完成IOC
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)) {
// 循环创建包装实例,完成AOP
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
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);
}
}
createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:
-
通过 getExtensionClasses 获取所有的拓展类
-
通过反射创建拓展对象
-
向拓展对象中注入依赖
-
将拓展对象包裹在相应的 Wrapper 对象中,通过包装类完成AOP
以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现
接下来我们来看看这四步Dubbo SPI怎么处理的:
1、获取所有的拓展类
private Map<String, Class<?>> getExtensionClasses() {
// 从缓存中获取已加载的拓展类
Map<String, Class<?>> classes = cachedClasses.get();
//双重检查
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//加载拓展类
classes = loadExtensionClasses();
//将拓展类加入缓存
cachedClasses.set(classes);
}
}
}
return classes;
}
这里也是先检查缓存,若缓存未命中,则通过 synchronized 加锁。加锁后再次检查缓存,并判空。此时如果 classes 仍为 null,则通过 loadExtensionClasses 加载拓展类。下面分析 loadExtensionClasses 方法的逻辑。
加载拓展类
private Map<String, Class<?>> loadExtensionClasses() {
//处理@SPI注解内容解析
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
for (LoadingStrategy strategy : strategies) {
// 加载指定文件夹下的配置文件,strategy.directory()分别包括dubbo,dubbo/internal,services
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}
return extensionClasses;
}
解析SPI注解,获取扩展类的配置,如:@SPI("bmw")
private void cacheDefaultExtensionName() {
// 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation == null) {
return;
}
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
// 对 SPI 注解内容进行逗号切分
String[] names = NAME_SEPARATOR.split(value);
// 检测 SPI 注解内容是否合法,不合法则抛出异常,其实就是说只能配一个.....
if (names.length > 1) {
throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
// 设置默认名称,参考 getDefaultExtension 方法
if (names.length == 1) {
cachedDefaultName = names[0];
}
}
}
回过头来看下IOC注入的方法:
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
// 遍历目标类的所有方法
for (Method method : instance.getClass().getMethods()) {
//检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 获取 setter 方法参数类型
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 获取属性名,比如 setName 方法对应属性名 name
String property = getSetterProperty(method);
// 从 O