在 Dubbo 中,很多扩展点都是通过Dubbo SPI机制进行加载的,比如 Transporter、Cluster、LoadBalance 等。有时,有些扩展并不想在框架启动阶段被加载,而是希望在扩展方法被调用时,根据运行时参数进行加载(按需加载。由于Java SPI机制有性能问题,Dubbo SPI对Java SPI做了一定优化)。Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类,最后再通过反射创建代理类。
如果大家对Dubbo SPI机制不熟悉的话,建议大家阅读笔者下面这篇文章,否则本节内容也许会有些难以理解。
从这篇文章中,大家可以学到的知识点如下:
-
Dubbo 扩展点自适应注解@Adaptive
-
Dubbo扩展点自适应类的生成
-
Netty是如何接入Dubbo的
在为大家介绍Netty如何接入Dubbo之前,需要大家深入理解Dubbo SPI机制和自适应扩展类相关的知识点,所以首先给大家介绍Dubbo自适应扩展类的加载。
扩展点自适应注解@Adaptive
根据Dubbo扩展点的自适应这一特性,Dubbo可以使用@Adaptive注解,动态的从URL参数中确定具体使用哪个扩展接口的实现类。@Adaptive使用示例如下(以Transporter接口为例)
@SPI("netty")
public interface Transporter {
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException;
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
我们可以看到,bind方法(connect方法类似)上标记了@Adaptive注解,该注解传入了两个参数Constants.SERVER_KEY和Constants.TRANSPORTER_KEY,外部调用Transporter.bind方法时,会动态的从传入的参数URL中提取key参数为Constants.SERVER_KEY的value值,如果能匹配上某个扩展类的实现类就直接使用对应的实现类,否则继续从URL参数中提取key参数为Constants.TRANSPORTER_KEY的value值,否则继续向下匹配(@Adaptive注解传入的是数组)。如果都没有匹配上,则使用@SPI注解中填写的默认值去匹配,如果还是没有匹配上,则抛IllegalStateException异常。
扩展点自适应类的生成
再三强调一下,如果大家对Dubbo SPI机制不熟悉,请务必阅读笔者的这篇文章。Apache Dubbo系列:增强SPI,否则下面的内容难以理解。
Dubbo生成自适应扩展类的流程如下:
-
查询缓存,如果cache miss,则创建自适应扩展类
-
创建自适应扩展类的方法是通过StringBuild类拼接Java code字符串代码
-
然后通过javassist或jdk(默认javassist)编译这串代码
-
最后通过反射创建代理类
org.apache.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension方法则是Dubbo生成扩展点自适应类的方法入口,所以我们首先分析这个方法
public T getAdaptiveExtension() {
// 从Adaptive cache中获取自适应扩展类实例
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// cache miss,创建自适应扩展类实例并写cache
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
// 返回自适应扩展类实例
return (T) instance;
}
上面的这段代码,逻辑非常简单,从cache中获取自适应扩展类实例,如果缓存中没有则创建并写缓存,接下来我们来看createAdaptiveExtension方法的实现逻辑
org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtension
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
通过getAdaptiveExtensionClass().newInstance()方法获取扩展类的实例,然后通过调用injectExtension方法返回扩展类实例(injectExtension方法在此不在展开详述,具体可以阅读笔者的Apache Dubbo系列:增强SPI文章,其实injectExtension方法的主要作用是通过反射获取传入参数的所有setter方法为其注入参数),所以下面我们重点分析getAdaptiveExtensionClass方法。
private Class<?> getAdaptiveExtensionClass() {
// 加载SPI扩展点
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 创建自适应扩展类的Class对象
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
重点看最后一行代码,继续跟进去,离重点越来越近
private Class<?> createAdaptiveExtensionClass() {
// 生成自适应扩展类代码,拼接成String
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
//获取类加载器
ClassLoader classLoader = findClassLoader();
// 获取编译器,javassist或jdk
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// 编译
return compiler.compile(code, classLoader);
}
上面的代码就很有意思,主要做了四件事
-
通过StringBuilder拼接起来的自适应扩展类的代码字符串。
-
获取类加载器。
-
获取编译器,默认使用javassist,具体使用javassist还是jdk,也是通过Dubbo SPI机制配置的。
-
编译返回自适应扩展类的Class。
继续跟进org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generate
public String generate() {
// 判断是否有@Adaptive注解的方法
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
StringBuilder code = new StringBuilder();
// 拼接package
code.append(generatePackageInfo());
// 拼接import
code.append(generateImports());
// 拼接public class XXX$Adaptive implements
code.append(generateClassDeclaration());
Method[] methods = type.getMethods();
// 拼接所有的方法
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
// 返回
return code.toString();
}
上面一段代码的重点是code.append方法,拼接package、import、class、method等,最后返回。强调一点,Dubbo生成的自适应扩展类的类名都是XXX$Adaptive,比如Transporter$Adaptive,Cluster$Adaptive等。我们跟进generateMethod这个方法看一下,因为这个方法比较有意思(其他generateXXX大同小异)。
private String generateMethod(Method method) {
// 获取方法返回值
String methodReturnType = method.getReturnType().getCanonicalName();
// 获取方法名
String methodName = method.getName();
// 获取方法体,此方法很复杂
String methodContent = generateMethodContent(method);
// 获取方法参数
String methodArgs = generateMethodArguments(method);
// 获取方法抛出的异常
String methodThrows = generateMethodThrows(method);
// 拼接
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}
当全部的自适应扩展类代码拼接完成后,调用compiler.compile方法编译成Class对象返回。另外提一下,生成的Compiler编译器也是通过Dubbo SPI机制配置的,Dubbo框架的灵活性和可扩展性,在SPI机制中体现的淋漓尽致。默认使用javassist去编译,代码如下
@SPI("javassist")
public interface Compiler {
Class<?> compile(String code, ClassLoader classLoader);
}
Dubbo生成自适应扩展类的代码分析到这里就结束了,最后我稍微为大家展示一下,Dubbo是如何接入Netty的。
Netty是何如接入Dubbo的
Netty是JAVA界最优秀的一个NIO框架,适合开发高性能的网络应用,Dubbo默认的网络通信框架是Netty,在这里我就不和大家做太多Netty的介绍了。
大家还记得文章开始的Transporter这个扩展点接口吗?
@SPI("netty")
public interface Transporter {
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException;
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
Transporter接口上的SPI注解的默认值是"netty",我们下面看一下Transporter接口的实现类
public class NettyTransporter implements Transporter {
public static final String NAME = "netty";
@Override
public RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException {
return new NettyServer(url, handler);
}
@Override
public Client connect(URL url, ChannelHandler handler) throws RemotingException {
return new NettyClient(url, handler);
}
}
bind方法和connect方法分别返回NettyServer和NettyClient,我们随便找一个跟进去,就可以看到我们大家熟悉的Netty代码啦,具体源码我们在本章不做太多分析,下节分享,我将和大家详细分析Dubbo与Netty的对接过程和源码。
今天的分享就到这里,谢谢大家。
扫描二维码,加作者微信,更多精彩