Dubbo源码分析(一)源码构建与启动流程

您所在的位置:网站首页 dubbo启动懒加载 Dubbo源码分析(一)源码构建与启动流程

Dubbo源码分析(一)源码构建与启动流程

2023-11-16 18:56| 来源: 网络整理| 查看: 265

待Dubbo3.0发版之际,出于好奇心和学习需求。又一次回归dubbo的源码细节

Dubbo源码构建

1.拉取源码

git clone https://github.com/apache/dubbo.git dubbo

Dubbo 使用 maven 作为构建工具。

Java 1.8 以上的版本 Maven 2.2.1 或者以上的版本

2.配置源码参数,构建之前需要配置以下的 MAVEN_OPTS 防止本地编译时运行内存不足

export MAVEN_OPTS=-Xmx1024m -XX:MaxPermSize=512m

3.编译源码

mvn clean install -Dmaven.test.skip

4.构建源代码jar包

mvn clean source:jar install -Dmaven.test.skip

dubbo3 概念预览

dubbo作为一个微服务框架,Dubbo SDK与应用服务绑定在同一个进程内,随着应用服务被部署在集群的各个位置。 为了能使得各个微服务之间协同工作,Dubbo定义了一些中心化组件,包括:

注册中心: 协调Consumer与Provider之间的地址注册与发现 配置中心: 存储Dubbo启动阶段的全局配置,保证配置的跨环境共享与全局一致性 负责服务治理规则(路由规则,动态配置等)的存储与推送 元数据中心

接受Provider上报的服务接口元数据,为Admin等控制台提供运维能力(如服务测试,接口文档等)

作为服务发现机制的补充,提供额外的接口/方法级别的配置信息同步能力,相当于注册中心的额外拓展

从xml看dubbo的启动流程

Dubbo是一个远程调用的RPC框架,对于每个我们封装的微服务都会以Invoker以及exporter的形式引用和暴露服务 源码分析参考: dubbo.apache.org/zh/docs/v2.…

provider以及consumer的xml配置请见: dubbo.apache.org/zh/docs/v2.…

下面以dubbo3的xml文件配置到启用服务引用做一个总结

dubbo xml完整demo : github.com/apache/dubb…

dubbo-provider-demo.xml

上述xml文件会被转化为如下java代码

public class ZookeeperDubboServiceProviderBootstrap { public static void main(String[] args) { DubboBootstrap.getInstance() .application("zookeeper-dubbo-provider", app -> app.metadata(COMPOSITE_METADATA_STORAGE_TYPE)) .registry(builder -> builder.address("yuluo-15:2181").protocol("zookeeper") .parameter(REGISTRY_TYPE_KEY, SERVICE_REGISTRY_TYPE)) .protocol("dubbo", builder -> builder.port(-1).name("dubbo")) .protocol("rest", builder -> builder.port(8081).name("rest")) .service("echo", builder -> builder.interfaceClass(EchoService.class).ref(new EchoServiceImpl()).protocolIds("dubbo")) .service("user", builder -> builder.interfaceClass(UserService.class).ref(new UserServiceImpl()).protocolIds("rest")) .start() .await(); } }

究竟是如何转换的?首先需要谈谈dubbo的类加载机制

基于dubbo SPI的简单拓展点加载

从dubbo2.7开始,不同于dubbo2.5单纯从xml当中读取配置属性进行解析,而是基于类加载机制的ExtensionLoader进行加载。基于extensionLoader可以为以后配置文件中新的配置属性定义多个拓展点

dubbo SPI源码工程: github.com/apache/dubb…

代码位置: dubbo-common/src/test 在其下新建一个文件夹添加样例如下

package org.apache.dubbo.test; import org.apache.dubbo.common.extension.SPI; @SPI public interface Robot { void sayHello(String str); } package org.apache.dubbo.test; public class FirstObject implements Robot{ @Override public void sayHello(String str) { System.out.println(str); } } package org.apache.dubbo.test; import org.apache.dubbo.common.extension.ExtensionLoader; import org.junit.jupiter.api.Test; import java.util.ServiceLoader; public class JavaSPITest { @Test public void sayHello() throws Exception{ ExtensionLoader serviceLoader = ExtensionLoader.getExtensionLoader(Robot.class); Robot robot = serviceLoader.getExtension("firstObject"); robot.sayHello("nice first object"); } }

效果如下

2022-01-04.png

dubbo的启动流程中拓展点的加载

那么dubbo的xml中的配置属性是如何通过拓展点加载的? 在Dubbo启动时期,需要加载FrameWorkModel,ApplicationModel,ConfigCenter,MetadataCenter等拓展点。在加载拓展点的过程当中会根据dubbo部署的配置文件 加载对应应用下的配置中心,元数据中心。并且逐个加载对应的接口实现类

根据拓展点类型的不同,分为三类拓展点

2022-01-04-18-01.png

dubbo.internal 内置的组件比如 配置文件,环境参数,以及服务消费者和服务提供者的拓展点,过滤器链的优先级顺序注解@Order ,分组注解Activate,以及负载均衡的拓展点AdaptiveClassCodeGenerator

dubbo.external 外置的组件比如一些参数转换类型的String2BooleanConverter以及测试类

dubbo.service 指定加载哪些拓展点 内置组件或者内置

看到DubboBootstrap的启动流程当中来

/** * Start the bootstrap */ public DubboBootstrap start() { if (started.compareAndSet(false, true)) { destroyed.set(false); ready.set(false); // 初始化各类拓展点之后,对外暴露服务 initialize(); if (logger.isInfoEnabled()) { logger.info(NAME + " is starting..."); } // 1. export Dubbo Services exportServices(); ..... ..... } public void initialize() { if (!initialized.compareAndSet(false, true)) { return; } //初始化ConfigManager 所有的外部依赖在这里进行初始化 ApplicationModel.initFrameworkExts(); startConfigCenter(); loadRemoteConfigs(); checkGlobalConfigs(); // @since 2.7.8 startMetadataCenter(); initMetadataService(); if (logger.isInfoEnabled()) { logger.info(NAME + " has been initialized!"); } }

点开initFrameWorkExts 方法,发现ExtensionLoader#getExtensionClasses通过双重锁的方式加载缓存

DubboBootstrap.java public static void initFrameworkExts() { Set exts = ExtensionLoader.getExtensionLoader(FrameworkExt.class).getSupportedExtensionInstances(); for (FrameworkExt ext : exts) { ext.initialize(); } } ExtensionLoader.java public Set getSupportedExtensions() { Map> getExtensionClasses() { Map 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); } // 向实例中注入依赖 injectExtension(instance); Set> getExtensionClasses() { // 从缓存中获取已加载的拓展类 Map> loadExtensionClasses() { // 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的 final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation != null) { 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..."); } // 设置默认名称,参考 getDefaultExtension 方法 if (names.length == 1) { cachedDefaultName = names[0]; } } } Map>(); // 加载指定文件夹下的配置文件 上述提及的拓展点dubbo.internal dubbo dubbo.service loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadDirectory(extensionClasses, DUBBO_DIRECTORY); loadDirectory(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }

loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件。SPI 注解解析过程比较简单,无需多说。下面我们来看一下 loadDirectory 做了哪些事情。

private void loadDirectory(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { try { BufferedReader reader = new BufferedReader( new InputStreamReader(resourceURL.openStream(), "utf-8")); try { String line; // 按行读取配置内容 while ((line = reader.readLine()) != null) { // 定位 # 字符 final int ci = line.indexOf('#'); if (ci >= 0) { // 截取 # 之前的字符串,# 之后的内容为注释,需要忽略 line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf('='); if (i > 0) { // 以等于号 = 为界,截取键与值 name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { // 加载类,并通过 loadClass 方法对类进行缓存 loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class..."); } } } } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class..."); } }

loadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作。loadClass 方法用于主要用于操作缓存,该方法的逻辑如下:

private void loadClass(Map clazz, String name) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("..."); } // 检测目标类上是否有 Adaptive 注解 if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null) { // 设置 cachedAdaptiveClass缓存 cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("..."); } // 检测 clazz 是否是 Wrapper 类型 } else if (isWrapperClass(clazz)) { Set>(); wrappers = cachedWrapperClasses; } // 存储 clazz 到 cachedWrapperClasses 缓存中 wrappers.add(clazz); // 程序进入此分支,表明 clazz 是一个普通的拓展类 } else { // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常 clazz.getConstructor(); if (name == null || name.length() == 0) { // 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 name name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("..."); } } // 切分 name String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { // 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键, // 存储 name 到 Activate 注解对象的映射关系 cachedActivates.put(names[0], activate); } for (String n : names) { if (!cachedNames.containsKey(clazz)) { // 存储 Class 到名称的映射关系 cachedNames.put(clazz, n); } Class c = extensionClasses.get(n); if (c == null) { // 存储名称到 Class 的映射关系 extensionClasses.put(n, clazz); } else if (c != clazz) { throw new IllegalStateException("..."); } } } } }

综上所述,ExtensionLoader通过调用loadClass 方法操作了不同的缓存,比如 cachedAdaptiveClass、cachedWrapper 改写了双亲委派机制需要从最顶级的类加载器加载的机制



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3