Apache Dubbo系列:Netty与Dubbo是如何对接的

在 Dubbo 中,很多扩展点都是通过Dubbo SPI机制进行加载的,比如 Transporter、Cluster、LoadBalance 等。有时,有些扩展并不想在框架启动阶段被加载,而是希望在扩展方法被调用时,根据运行时参数进行加载(按需加载。由于Java SPI机制有性能问题,Dubbo SPI对Java SPI做了一定优化)。Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类,最后再通过反射创建代理类。

如果大家对Dubbo SPI机制不熟悉的话,建议大家阅读笔者下面这篇文章,否则本节内容也许会有些难以理解。

Apache 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的对接过程和源码。

今天的分享就到这里,谢谢大家。

扫描二维码,加作者微信,更多精彩

猜你喜欢

转载自blog.csdn.net/nuoWei_SenLin/article/details/107624360