所谓 Dubbo 的内核是指,Dubbo 中所有功能都是基于它之上完成的,都是由它作为基础的。Dubbo 内核的工作原理由四部分构成:
- SPI:服务发现机制
- Adaptive:自适应机制
- Wrapper:包装机制
- Activate:激活机制
动态编译的实现
Dubbo SPI的自适应特性让整个框架非常灵活, 而动态编译又是自适应特性的基础, 因为动态生成的自适应类只是字符串, 需要通过编译才能得到真正的Class0虽然我们可以使用反射来动态代理一个类, 但是在性能上和直接编译好的Class会有一定的差距。
Dubbo SPI通过代码的动态生成, 并配合动态编译器, 灵活地在原始类基础上创建新的自适应类。
Dubbo中的三种动态编译器
Compiler接口上含有一个SPI注解, 注解的默认值是@SPI(”javassist”),很明显, Javassist编译器将作为默认编译器。 如果用户想改变默认编译器, 则可以通过
<dubbo:application compiler="jdk"/>
AdaptiveCompiler动态编译
AdaptiveCompiler上面有@Adaptive注解, 说明AdaptiveCompiler会固定为默认实现这个Compiler的主要作用和AdaptiveExtensionFactory相似, 就是为了管理其他Compile。
AdaptiveCompiler部分源码
//设置默认的编译器名称
public static void setDefaultCompiler(String compiler) (
DEFAULTJZOMPILER = compiler;
}
public Class<?> compile(String code, ClassLoader classLoader) {
......
//通过ExtensionLoader获取对应的编译器扩展实现,调用真正的compile做编译
return compiler.comile(code, classLoader);
}
AdaptiveCompiler#setDefaultCompiler 方法会在 ApplicationConfig 中被调用, 也就是 Dubbo在启动时会扫描解析配置中的<dubbo:application compiler="jdk"/> 标签。如果没有配置此值,则会使用 @SPI("javassist") 注解中的默认动态编译方式。
AbstpactCompiler,它是一个抽象类, 无法实例化, 但在里面封装了通用的模板逻辑。 还定义了一个抽象方法decompile,留给子类来实现具体的编译逻辑。OavassistCompiler和DdkCompiler都实现了这个抽象方法。
Abstractcompiler的主要抽象逻辑如下:
- 通过正则匹配出包路径、 类名, 再根据包路径、 类名拼接出全路径类名。
- 尝试通过Class.forName加载该类并返回, 防止重复编译。 如果类加载器中没有这个类, 则进入第3步。
- 调用doCompile方法进行编译。 这个抽象方法由子类实现。
JDK动态编译
JDK原生编译器位于javax.tools包下。主要使用JavaFileObject接口,ForwardingJavaFileManager接口,JavaCompiler.CompilationTask方法。
编译过程为:首先初始化一个JavaFileObject对象,然后把代码字符串作为参数传入构造方法,然后调用JavaCompiler.CompilerationTash方法编译出具体的类。
- JavaFileObject:字符串代码会被包装成一个文件对象,并且提供获取二进制流的接口。在Dubbo框架中有一个类叫做JavaFileObjectImpl 这个类可以看做是JavaFileObject这个接口的实现扩展,构造方法需要传入生成好的字符串代码,此文件对象输入、输出流都是ByteArray 流。
- JavaFileManager:主要管理文件的读取和输出位置。JDK中没有可以直接使用的实现类,有个实现类叫ForwardingJavaFileManager构造器又是protect类型的。所以在Dubbo框架中扩张了一个JavaFileManagerImpl的实现类,然后通过一个自定义类加载器ClassLoaderImpl来完成资源的加载。
- JavaCompiler.CompilationTask把JavaFileObject来编译成具体的类。
Javassist动态编译
Javassist代码示例
public static void main(String[] args){
//初始化一个Javassist类池
ClassPool classPool = ClassPool.getDefault();
//创建一个Hello World 类
CtClass ctClass = classPool.makeClass("Hello World");
//添加一个test方法,会打印Hello World,直接传入方法的字符串
CtMethod ctMethod = CtNewMethod.make("
public static void test(){
System.out・printin(\"Hello World\");
}", ctClass);
ctClass.addMethod(ctMethod);
//生成类
Class aClass = ctClass.toClass();
//使用反射调用实例
Object object = aClass.newlnstance();
Method m = aClass.getDeclaredMethod("test"null);
m.invoke(objectnull);
}
在JavassistCompiler中, 就是不断通过正则表达式匹,配不同部位的代码, 然后调用Javassist库中的API生成不同部位的代码, 最后得到一个完整的Class对象。
在Dubbo框架中JavassistCompiler具体的步骤为:
- 初始化Javassist,设置默认参数, 如设置当前的classpath。
- 通过正则匹配出所有import的包, 并使用Javassist添加import。
- 通过正则匹配出所有extends的包, 创建Class对象, 并使用Javassist添加extends。
- 通过正则匹配出所有implements包, 并使用Javassist添加implements。
- 通过正则匹配出类里面所有内容, 即得到{} 中的内容, 再通过正则匹配出所有方法,并使用Javassist添加类方法。
- 生成Class对象。
JDK服务发现机制SPI
SPI,全称Service Provider Interface,服务提供者接口,是一种服务发现机制。
JDK中SPI规范
- 接口名:可随意定义。
- 实现类名:可随意定义。
- 提供者配置文件路径:查找的目录为 META-INF/services。
- 提供者配置文件名称:接口的全限定性类名,没有扩展名。
- 提供者配置文件内容:该接口的所有实现类的全限类性类名写入到该文件中,一个类名占一行。
代码例子
定义具体接口
package com.gc.service;
/**
* @description: 定义接口
**/
public interface GcService {
void habit();
}
定义具体实现类1
package com.gc.service.impl;
import com.gc.service.GcService;
/**
* @description: GC的生活习惯
**/
public class GcLivingHabitsServiceImpl implements GcService {
@Override
public void habit() {
System.out.println("这里是GC的 [生活] 习惯");
}
}
定义具体实现类2
package com.gc.service.impl;
import com.gc.service.GcService;
/**
* @description: GC的工作习惯
**/
public class GcWorkHabitsServiceImpl implements GcService {
@Override
public void habit() {
System.out.println("这里是GC的 [工作] 习惯");
}
}
在查找路径下定义配置信息文件:com.gc.service.GcService
com.gc.service.impl.GcLivingHabitsServiceImpl
com.gc.service.impl.GcWorkHabitsServiceImpl
编写测试类
package com.gc.test;
import com.gc.service.GcService;
import java.util.ServiceLoader;
/**
* @description: 测试类
**/
public class SPI {
public static void main(String[] args) {
ServiceLoader<GcService> load = ServiceLoader.load(GcService.class);
load.forEach(gc->gc.habit());
}
}
项目整体结构
运行结果
Dubbo服务发现机制SPI
Dubbo 并没有直接使用 JDK 的 SPI,而是在其基础之上对其进行了兼容改进。
@SPI注解可以使用在类、接口和枚举类上,Dubbo框架中都是使用在接口上。它的主要作用就是标记这个接口是一个Dubbo SPI接口,即是一个扩展点,可以有多个不同的内置或用户定义的实现。运行时需要通过配置找到具体的实现类。
@SPI注解的源码
package org.apache.dubbo.common.extension;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
//默认实现的key名称
String value() default "";
}
我们可以看到SPI注解有一个value属性,通过这个属性,我们可以传入不同的参数来设置这个接口的默认实现类。
Dubbo中SPI规范
- 接口名:可以随意定义。
- 实现类名:在接口名前添加一个用于表示自身功能的“标识前辍”字符串。
- 提供者配置文件路径:可放置的目录及依次查找的顺序为:
- META-INF/dubbo/internal
- META-INF/dubbo
- META-INF/services
- 提供者配置文件名称:接口的全限定性类名,无需扩展名。
- 提供者配置文件内容:文件的内容为 key=value 形式,value 为该接口的实现类的全限类性类名,key 可以随意,但一般为该实现类的“标识前辍”(首字母小写)。一个类名占一行。
- 提供者加载:ExtensionLoader 类相当于 JDK SPI 中的 ServiceLoader 类,用于加载提供者配置文件中所有的实现类,并创建相应的实例。
代码例子
pom.xml
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.3</version>
</dependency>
定义具体接口
package com.gc.service;
import org.apache.dubbo.common.extension.SPI;
/**
* @description: 定义接口
**/
//如果没有传入指定的调用类,则默认调用living
@SPI("living")
public interface Habits {
void habit();
}
具体实现1
package com.gc.service.impl;
import com.gc.service.Habits;
/**
* @description: GC的生活习惯
**/
public class LivingHabits implements Habits {
@Override
public void habit() {
System.out.println("这里是GC的 [生活] 习惯");
}
}
具体实现2
package com.gc.service.impl;
import com.gc.service.Habits;
/**
* @description: GC的工作习惯
**/
public class WorkHabits implements Habits {
@Override
public void habit() {
System.out.println("这里是GC的 [工作] 习惯");
}
}
在查找路径下定义配置信息文件:com.gc.service.Habits
living=com.gc.service.impl.LivingHabits
work=com.gc.service.impl.WorkHabits
编写测试类
package com.gc.test;
import com.gc.service.Habits;
import org.apache.dubbo.common.extension.ExtensionLoader;
/**
* @description: 测试类
**/
public class SPI {
public static void main(String[] args) {
ExtensionLoader<Habits> loader = ExtensionLoader.getExtensionLoader(Habits.class);
//调用默认实现
loader.getExtension("true").habit();
//调用指定实现
loader.getExtension("work").habit();
}
}
项目结构
运行结果
ExtensionLoader 的工作流程
ExtensionLoader是整个扩展机制的主要逻辑类在这个类里面卖现了配置的加载,扩展类缓存,自适应对象生成等所有工作。
ExtensionLoader 的逻辑入口可以分为 getExtensio,getAdaptiveExtensio,getActivateExtension三个分别是获取普通扩展类、获取自适应扩展类、获取自动激活的扩展类。总体逻辑都是从调用这三个方法开始的,每个方法可能会有不同的重载的方法,根据不同的传入参数进行调整。
关于ExtensionLoader 的工作流程这里先简单提一嘴,后面我会分析它的源代码。
ExtensionLoader的工作流程图
自适应机制 Adaptive
Adaptive 机制,即扩展类的自适应机制即其可以指定想要加载的扩展名,也可以不指定。若不指定,则直接加载默认的扩展类。即其会自动匹配,做到自适应。其是通过@Adaptive注解实现的。一个 SPI 接口最多只会有一个 Adaptive 类(自定义的或自动生成的)
@Adaptive 注解
@Adaptive 注解可以修饰类与方法,其作用相差很大
//@Adaptive注解源码
public @interface Adaptive (
//数组,可以设置多个key,会按顺序依次匹配
String[] value() default {};
}
- @Adaptive 修饰方法
被@Adapative 修饰的 SPI 接口中的方法称为 Adaptive 方法。在 SPI 扩展类中若没有找到Adaptive 类,但系统却发现了 Adapative 方法,就会根据 Adaptive 方法自动为该 SPI 接口动态生成一个 Adaptive 扩展类,并自动将其编译。例如 Protocol 接口中就包含两个 Adaptive方法。
-
@Adaptive 修饰类
有些 SPI 接口中的方法不需要 URL 相关的参数,此时就可以直接让@Adaptivate 来修饰某个 SPI 接口的实现类,由该类实现对 SPI 扩展类的自适应。
其是装饰者设计模式的应用。
Adaptive 方法规范
动态生成 Adaptive 类格式
package <SPI 接口所在包>;
public class SPI 接口名$Adpative implements SPI 接口 {
public adaptiveMethod (arg0, arg1, ...) {
// 注意,下面的判断仅对 URL 类型,或可以获取到 URL 类型值的参数进行判断
// 例如,dubbo 的 Invoker 类型中就包含有 URL 属性
if(arg1==null)
throw new IllegalArgumentException(异常信息);
if(arg1.getUrl()==null)
throw new IllegalArgumentException(异常信息);
URL url = arg1.getUrl();
// 其会根据@Adaptive 注解上声明的 Key 的顺序,从 URL 获取 Value,
// 作为实际扩展类。若有默认扩展类,则获取默认扩展类名;否则获取
// 指定扩展名名。
String extName = url.get 接口名() == null?默认扩展前辍名:url.get 接口名();
if(extName==null) throw new IllegalStateException(异常信息);
SPI 接口 extension = ExtensionLoader.getExtensionLoader(SPI 接口.class)
.getExtension(extName);
return extension. adaptiveMethod(arg0, arg1, ...);
}
public unAdaptiveMethod( arg0, arg1, ...) {
throw new UnsupportedOperationException(异常信息);
}
}
方法规范
从前面的动态生成的 Adaptive 类中的 adaptiveMethod()方法体可知,其对于要加载的扩展名的指定方式是通过 URL 类型的方法参数指定的。所以对于 Adaptive 方法的定义规范仅一条:其参数包含 URL 类型的参数,或参数可以获取到 URL 类型的值。方法调用者是通过URL 传递要加载的扩展名的。
代码例子
定义具体接口
package com.gc.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
/**
* @description: 定义接口
**/
//如果没有传入指定的调用类,则默认调用living
@SPI("work")
public interface Habits {
//好习惯
void goodHabit();
//坏习惯
@Adaptive
void badHabit(URL url);
}
具体实现1
package com.gc.service.impl;
import com.gc.service.Habits;
import org.apache.dubbo.common.URL;
/**
* @description: GC的生活习惯
**/
public class LivingHabits implements Habits {
@Override
public void goodHabit() {
System.out.println("这是GC的 (生活) [好习惯]");
}
@Override
public void badHabit(URL url) {
System.out.println("这是GC的 (生活) [坏习惯]");
}
}
具体实现2
package com.gc.service.impl;
import com.gc.service.Habits;
import org.apache.dubbo.common.URL;
/**
* @description: GC的工作习惯
**/
public class WorkHabits implements Habits {
@Override
public void goodHabit() {
System.out.println("这是GC的 (工作) [好习惯]");
}
@Override
public void badHabit(URL url) {
System.out.println("这是GC的 (工作) [坏习惯]");
}
}
编写测试类
package com.gc.test;
import com.gc.service.Habits;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
/**
* @description: 测试类
**/
public class SPI {
public static void main(String[] args) {
ExtensionLoader<Habits> loader = ExtensionLoader.getExtensionLoader(Habits.class);
//调用指定实现
Habits habits = loader.getAdaptiveExtension();
//模拟一个URL
//如果不不指定调用实现类,则调用@SPI注解中指定的默认实现扩展类
//URL url = URL.valueOf("xxx://127.0.0.1/xxx");
//如果在url后面指定了要调用的扩展,则直接调用该扩展。
URL url = URL.valueOf("xxx://127.0.0.1/xxx?habits=work");
habits.badHabit(url);
}
}
包装机制Wrapper
Wrapper 机制,即扩展类的包装机制。就是对扩展类中的 SPI 接口方法进行增强,进行包装,是AOP思想的提现是 Wrapper 设计模式的应用。一个 SPI 可以包含多个 Wrapper。
Wrapper规范
Wrapper并不是通过注解实现的,而是通过一套Wrapper规范。
- 类需要实现SPI接口
- 类中需要有SPI接口的引用
- 类中的SPI实例需要是通过一个包含一个SIP接口参数的带参构造器来传的
- 在接口实现方法中要调用SPI接口引用对象的相应方法
- 类名的后缀需要时Wrapper
代码例子
定义具体接口
package com.gc.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
/**
* @description: 定义接口
**/
//如果没有传入指定的调用类,则默认调用living
@SPI("work")
public interface Habits {
//好习惯
void goodHabit();
//坏习惯
@Adaptive
void badHabit(URL url);
}
具体实现1
package com.gc.service.impl;
import com.gc.service.Habits;
import org.apache.dubbo.common.URL;
/**
* @description: GC的生活习惯
**/
public class LivingHabits implements Habits {
@Override
public void goodHabit() {
System.out.println("这是GC的 (生活) [好习惯]");
}
@Override
public void badHabit(URL url) {
System.out.println("这是GC的 (生活) [坏习惯]");
}
}
具体实现2
package com.gc.service.impl;
import com.gc.service.Habits;
import org.apache.dubbo.common.URL;
/**
* @description: GC的工作习惯
**/
public class WorkHabits implements Habits {
@Override
public void goodHabit() {
System.out.println("这是GC的 (工作) [好习惯]");
}
@Override
public void badHabit(URL url) {
System.out.println("这是GC的 (工作) [坏习惯]");
}
}
对具体实现进行包装
package com.gc.service.impl;
import com.gc.service.Habits;
import org.apache.dubbo.common.URL;
/**
* TODO 包装增强类
* 1.实现了Habits(SPI)接口
* 2.在此包装类中定义包装接口的变量引用
* 3.使用有参构造器传递具体包装类注入
* 4.调用SPI接口中的方法进行包装
*/
public class HabitsWrapper implements Habits {
private Habits habits;
public HabitsWrapper(Habits habits){
this.habits = habits;
}
@Override
public void goodHabit() {
System.out.println("在此之前,对GC的【好习惯】好习惯在此增强----开始");
habits.goodHabit();
System.out.println("增强----结束");
}
@Override
public void badHabit(URL url) {
System.out.println("在此之前,对GC的【坏习惯】坏习惯在此增强----开始");
habits.badHabit(url);
System.out.println("增强----结束");
}
}
编写测试类
package com.gc.service.test;
import com.gc.service.Habits;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
/**
* TODO 测试类
*/
public class Test {
public static void main(String[] args) {
ExtensionLoader<Habits> loader = ExtensionLoader.getExtensionLoader(Habits.class);
Habits habits = loader.getAdaptiveExtension();
URL url = URL.valueOf("xxx://localhost:8080/ooo/jjj");
habits.badHabit(url);
}
}
在查找路径下定义配置信息文件:com.gc.service.Habits
babits=com.gc.service.impl.LivingHabits
work=com.gc.service.impl.WorkHabits
wrapper=com.gc.service.impl.HabitsWrapper
项目结构
激活机制Activae
用于一次激活多个扩展类的。
Activate 机制,即扩展类的激活机制。通过指定的条件来激活当前的扩展类。其是通过@Active 注解实现的。
@Activae注解
在@Activate 注解中共有五个属性,其中 before、after 两个属性已经过时,剩余有效属性还有三个。它们分别是:
- group: 为扩展类指定所属的组别,是当前扩展类的一个标识。String[]类型,表示一个扩展类可以属于多个组。
- value: 为当前扩展类指定的 key,是当前扩展类的一个标识。String[]类型,表示一个扩展类可以有多个指定的 key。
- order: 指定筛选条件相同的扩展类的加载顺序。序号越小,优先级越高。默认值为 0。
代码例子
我们已线上,线下支付举例。把线上和线下不同的支付方法分为不同的组。其中比如银行卡支付,它既可以是线上支付也可以是线下支付,所以它的处境可以有多个分组。
具体接口
package com.gc.service;
import org.apache.dubbo.common.extension.SPI;
/**
* TODO 支付方式
*/
@SPI("alipay")
public interface Pay {
void pay();
}
具体实现1
package com.gc.service.impl;
import com.gc.service.Pay;
import org.apache.dubbo.common.extension.Activate;
/**
* TODO 支付宝支付
*/
@Activate(group = "online", value = "alipay", order = 2)
public class AlipayPay implements Pay {
@Override
public void pay() {
System.out.println("支付宝支付");
}
}
具体实现2
package com.gc.service.impl;
import com.gc.service.Pay;
import org.apache.dubbo.common.extension.Activate;
/**
* TODO 银行卡支付
*
* @author GC
* @date 2020/12/21 11:23 上午
*/
@Activate(group = {"online","offline"}, order = 3)
public class CardPay implements Pay {
@Override
public void pay() {
System.out.println("银行卡支付");
}
}
具体实现3
package com.gc.service.impl;
import com.gc.service.Pay;
import org.apache.dubbo.common.extension.Activate;
/**
* TODO 现金支付
*
* @author GC
* @date 2020/12/21 11:26 上午
*/
@Activate(group = "offline")
public class CashPay implements Pay {
@Override
public void pay() {
System.out.println("现金支付");
}
}
具体实现4
package com.gc.service.impl;
import com.gc.service.Pay;
import org.apache.dubbo.common.extension.Activate;
/**
* TODO 购物券支付
*/
@Activate(group = {"online", "offline"}, order = 1)
public class CouponPay implements Pay {
@Override
public void pay() {
System.out.println("购物券支付");
}
}
具体实现5
package com.gc.service.impl;
import com.gc.service.Pay;
import org.apache.dubbo.common.extension.Activate;
/**
* TODO 微信支付
*/
@Activate(group = "online")
public class WechatPay implements Pay {
@Override
public void pay() {
System.out.println("微信支付");
}
}
编写测试类
package com.gc.service.test;
import com.gc.service.Pay;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
import java.util.List;
import java.util.Set;
/**
* TODO 测试类
*/
public class Test {
//激活所有group为online的扩展类
/*public static void main(String[] args) {
ExtensionLoader<Pay> loader = ExtensionLoader.getExtensionLoader(Pay.class);
URL url = URL.valueOf("xxx://localhost:8080/ooo/jjj");
List<Pay> online = loader.getActivateExtension(url, "", "online");
for (Pay pay : online) {
pay.pay();
}
}*/
//激活所有group为offline的扩展类
/*public static void main(String[] args) {
ExtensionLoader<Pay> loader = ExtensionLoader.getExtensionLoader(Pay.class);
URL url = URL.valueOf("xxx://localhost:8080/ooo/jjj");
List<Pay> offline = loader.getActivateExtension(url, "", "offline");
for (Pay pay : offline) {
pay.pay();
}
}*/
//按照指定的key激活的扩展类
/*public static void main(String[] args) {
ExtensionLoader<Pay> loader = ExtensionLoader.getExtensionLoader(Pay.class);
URL url = URL.valueOf("xxx://localhost:8080/ooo/jjj?xxx=alipay");
//key 和 group是 或的关系
List<Pay> offline = loader.getActivateExtension(url, "xxx", "online");
for (Pay pay : offline) {
pay.pay();
}
}*/
//获取所有的激活类
public static void main(String[] args) {
ExtensionLoader<Pay> loader = ExtensionLoader.getExtensionLoader(Pay.class);
Set<String> set = loader.getSupportedExtensions();
System.out.println(set);
}
}
在查找路径下定义配置信息文件:com.gc.service.Pay
alipay=com.gc.service.impl.AlipayPay
wechat=com.gc.service.impl.WechatPay
card=com.gc.service.impl.CardPay
cash=com.gc.service.impl.CashPay
coupon=com.gc.service.impl.CouponPay
项目结构图
@Activate 的 group 与 value 属性表示该扩展类的两种不同的标识。它们的用法是:
- 当仅指定了 group 这个大范围标识,当前扩展类可以通过 group 来激活,也可以通过扩展名来激活。
- 一旦指定了 value 这个小范围标识,其就会将 group 这个大范围标识给屏蔽,即其只能通过扩展名来激活,不能通过 group 来激活。
总结
配置文件中可能会存在四种类:普通扩展类,Adaptive 类,Wrapper 类,及 Activate 类。它们的共同点是:都实现了 SPI 接口。它们的不同点有:
- 定义方式:Adaptive 类与 Activate 类都是通过注解定义的。
- 数量:一个 SPI 接口的 Adaptive 类最多只能有一个(无论是自定义的,还是自动生成的),而 Wrapper 类与 Activate 类可以有多个。
- 直接扩展类:Adaptive 类与 Wrapper 类不是直接扩展类。