1. 首页
  2. Dubbo源码解析
  3. Dubbo 源码分析 —— API 配置(二)之服务提供者

Dubbo 源码分析 —— API 配置(二)之服务提供者

  • 发布于 2024-08-11
  • 7 次阅读

本文基于 Dubbo 2.6.1 版本,望知悉。

友情提示,【配置】这块的内容,会相对比较枯燥。所以,如果看到一些很难懂的地方,建议先跳过。

对于 Dubbo ,重点是要去理解,多协议、RPC、容错等等模块,而不是【配置】。

😈 估计好多胖友被【配置】这章劝退了把???

1. 概述

本文接 《API 配置(一)之应用》 ,分享服务提供者相关的配置:包括 provider-config 和 sub-config 部分。

  • 黄框部分,provider-side

  • 其他部分,sub-config

还是老样子,我们先来看一段 《Dubbo 用户指南 —— API 配置》 ,服务提供者的初始化代码:

// 服务实现
XxxService xxxService = new XxxServiceImpl();

// 当前应用配置
ApplicationConfig application = new ApplicationConfig();
application.setName("xxx");

// 连接注册中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("10.20.130.230:9090");
registry.setUsername("aaa");
registry.setPassword("bbb");

// 服务提供者协议配置
ProtocolConfig protocol = new ProtocolConfig();
protocol.setName("dubbo");
protocol.setPort(12345);
protocol.setThreads(200);

// 注意:ServiceConfig为重对象,内部封装了与注册中心的连接,以及开启服务端口

// 服务提供者暴露服务配置
ServiceConfig<XxxService> service = new ServiceConfig<XxxService>(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
service.setApplication(application);
service.setRegistry(registry); // 多个注册中心可以用setRegistries()
service.setProtocol(protocol); // 多个协议可以用setProtocols()
service.setInterface(XxxService.class);
service.setRef(xxxService);
service.setVersion("1.0.0");

// 暴露及注册服务
service.export();
  • 相比 ReferenceConfig 的初始化,会多创建 ProtocolConfig 对象,设置到 ServiceConfig 对象中。


友情提示:本文前面部分会比较琐碎,重点在 「8. ServiceConfig」 部分。

2. ProtocolConfig

com.alibaba.dubbo.config.ProtocolConfig ,服务提供者协议配置。

3. AbstractMethodConfig

com.alibaba.dubbo.config.AbstractMethodConfig ,方法级配置的抽象类。

4. MethodConfig

com.alibaba.dubbo.config.MethodConfig ,继承 AbstractMethodConfig ,方法级配置。

5. AbstractInterfaceConfig

com.alibaba.dubbo.config.AbstractInterfaceConfig ,继承 AbstractMethodConfig ,抽象接口配置类。

  • 具体属性的解释,需要寻找《Dubbo 用户指南 —— dubbo:service》《Dubbo 用户指南 —— dubbo:reference》 文档。

  • 下面的方法,会在 「8. ServiceConfig」 的初始化被调用,胖友可需要的时候,点击查看。

  • #checkApplication() 方法,校验 ApplicationConfig 配置。实际上,该方法会初始化 ApplicationConfig 的配置属性。

    • 🙂 直接点击方法查看,较为简单,已经添加详细注释。

  • #checkRegistry() 方法,校验 RegistryConfig 配置。实际上,该方法会初始化 RegistryConfig 的配置属性。

    • 🙂 直接点击方法查看,较为简单,已经添加详细注释。

  • #checkInterfaceAndMethods(interfaceClass, methods) 方法,校验接口和方法。主要是两方面:

    • 1、 接口类非空,并是接口

    • 2、 方法在接口中已定义

    • 🙂 直接点击方法查看,较为简单,已经添加详细注释。

  • #checkStubAndMock(interfaceClass) 方法,校验 Stub 和 Mock 相关的配置。

  • 以上未列举的 #loadRegistries(provider)#loadMonitor(registryURL) 方法,在后续文章需要使用到时,在详细分享。

6. AbstractServiceConfig

com.alibaba.dubbo.config.AbstractServiceConfig ,实现 AbstractInterfaceConfig ,抽象服务配置类。

7. ProviderConfig

com.alibaba.dubbo.config.ProviderConfig ,实现 AbstractServiceConfig ,服务提供者缺省值配置。

8. ServiceConfig

com.alibaba.dubbo.config.ServiceConfig ,服务提供者暴露服务配置类

下面,我们进入正戏

在文初的 ServiceConfig 的初始化示例代码中,最后调用的是 ServiceConfig#export() 方法。从方法的命名,我们可以看出,暴露服务。该方法主要做了如下几件事情:

  1. 进一步初始化 ServiceConfig 对象。

  2. 校验 ServiceConfig 对象的配置项。

  3. 使用 ServiceConfig 对象,生成 Dubbo URL 对象数组

  4. 使用 Dubbo URL 对象,暴露服务

😈 本文重点在服务提供者相关的配置,因此只解析 1+2+3 部分( 不包括 4 )。代码如下

 1: public synchronized void export() {
 2:     // 当 export 或者 delay 未配置,从 ProviderConfig 对象读取。
 3:     if (provider != null) {
 4:         if (export == null) {
 5:             export = provider.getExport();
 6:         }
 7:         if (delay == null) {
 8:             delay = provider.getDelay();
 9:         }
10:     }
11:     // 不暴露服务( export = false ) ,则不进行暴露服务逻辑。
12:     if (export != null && !export) {
13:         return;
14:     }
15: 
16:     // 延迟暴露
17:     if (delay != null && delay > 0) {
18:         delayExportExecutor.schedule(new Runnable() {
19:             public void run() {
20:                 doExport();
21:             }
22:         }, delay, TimeUnit.MILLISECONDS);
23:     // 立即暴露
24:     } else {
25:         doExport();
26:     }
27: }
  • 第 2 至 10 行:当 exportdelay 未配置时,从 ProviderConfig 对象读取。

  • 第 11 至 14 行:当配置不需要暴露服务( export = false )时,直接返回。

  • 第 17 至 22 行:当配置延迟暴露( delay > 0 )时,使用 delayExportExecutor 延迟调度,调用 #doExport() 方法。

    《Dubbo 用户指南 —— 延迟暴露》

  • 第 23 至 26 行:立即暴露,调用 #doExport() 方法。

#doExport() 方法,代码如下:

 1: protected synchronized void doExport() {
  2:     // 检查是否可以暴露,若可以,标记已经暴露。
  3:     if (unexported) {
  4:         throw new IllegalStateException("Already unexported!");
  5:     }
  6:     if (exported) {
  7:         return;
  8:     }
  9:     exported = true;
 10:     // 校验接口名非空
 11:     if (interfaceName == null || interfaceName.length() == 0) {
 12:         throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
 13:     }
 14:     // 拼接属性配置(环境变量 + properties 属性)到 ProviderConfig 对象
 15:     checkDefault();
 16:     // 从 ProviderConfig 对象中,读取 application、module、registries、monitor、protocols 配置对象。
 17:     if (provider != null) {
 18:         if (application == null) {
 19:             application = provider.getApplication();
 20:         }
 21:         if (module == null) {
 22:             module = provider.getModule();
 23:         }
 24:         if (registries == null) {
 25:             registries = provider.getRegistries();
 26:         }
 27:         if (monitor == null) {
 28:             monitor = provider.getMonitor();
 29:         }
 30:         if (protocols == null) {
 31:             protocols = provider.getProtocols();
 32:         }
 33:     }
 34:     // 从 ModuleConfig 对象中,读取 registries、monitor 配置对象。
 35:     if (module != null) {
 36:         if (registries == null) {
 37:             registries = module.getRegistries();
 38:         }
 39:         if (monitor == null) {
 40:             monitor = module.getMonitor();
 41:         }
 42:     }
 43:     // 从 ApplicationConfig 对象中,读取 registries、monitor 配置对象。
 44:     if (application != null) {
 45:         if (registries == null) {
 46:             registries = application.getRegistries();
 47:         }
 48:         if (monitor == null) {
 49:             monitor = application.getMonitor();
 50:         }
 51:     }
 52:     // 泛化接口的实现
 53:     if (ref instanceof GenericService) {
 54:         interfaceClass = GenericService.class;
 55:         if (StringUtils.isEmpty(generic)) {
 56:             generic = Boolean.TRUE.toString();
 57:         }
 58:     // 普通接口的实现
 59:     } else {
 60:         try {
 61:             interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());
 62:         } catch (ClassNotFoundException e) {
 63:             throw new IllegalStateException(e.getMessage(), e);
 64:         }
 65:         // 校验接口和方法
 66:         checkInterfaceAndMethods(interfaceClass, methods);
 67:         // 校验指向的 service 对象
 68:         checkRef();
 69:         generic = Boolean.FALSE.toString();
 70:     }
 71:     // 处理服务接口客户端本地代理( `local` )相关。实际目前已经废弃,使用 `stub` 属性,参见 `AbstractInterfaceConfig#setLocal` 方法。
 72:     if (local != null) {
 73:         // 设为 true,表示使用缺省代理类名,即:接口名 + Local 后缀
 74:         if ("true".equals(local)) {
 75:             local = interfaceName + "Local";
 76:         }
 77:         Class<?> localClass;
 78:         try {
 79:             localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
 80:         } catch (ClassNotFoundException e) {
 81:             throw new IllegalStateException(e.getMessage(), e);
 82:         }
 83:         if (!interfaceClass.isAssignableFrom(localClass)) {
 84:             throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
 85:         }
 86:     }
 87:     // 处理服务接口客户端本地代理( `stub` )相关
 88:     if (stub != null) {
 89:         // 设为 true,表示使用缺省代理类名,即:接口名 + Stub 后缀
 90:         if ("true".equals(stub)) {
 91:             stub = interfaceName + "Stub";
 92:         }
 93:         Class<?> stubClass;
 94:         try {
 95:             stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
 96:         } catch (ClassNotFoundException e) {
 97:             throw new IllegalStateException(e.getMessage(), e);
 98:         }
 99:         if (!interfaceClass.isAssignableFrom(stubClass)) {
100:             throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
101:         }
102:     }
103:     // 校验 ApplicationConfig 配置。
104:     checkApplication();
105:     // 校验 RegistryConfig 配置。
106:     checkRegistry();
107:     // 校验 ProtocolConfig 配置数组。
108:     checkProtocol();
109:     // 读取环境变量和 properties 配置到 ServiceConfig 对象。
110:     appendProperties(this);
111:     // 校验 Stub 和 Mock 相关的配置
112:     checkStubAndMock(interfaceClass);
113:     // 服务路径,缺省为接口名
114:     if (path == null || path.length() == 0) {
115:         path = interfaceName;
116:     }
117:     // 暴露服务
118:     doExportUrls();
119:     // TODO,等待 qos
120:     ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
121:     ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
122: }
  • 第 2 至 9 行:检查是否可以暴露。若可以,标记已经暴露( exported = true )。

  • 第 10 至 13 行:校验接口名 interfaceName 非空。

  • 第 15 行:调用 #checkDefault() 方法,读取属性配置( 环境变量 + properties 属性 )到 ProviderConfig 对象。

  • 第 16 至 33 行:从 ProviderConfig 对象中,读取 applicationmoduleregistriesmonitorprotocols 对象。

  • 第 34 至 42 行:从 ModuleConfig 对象中,读取 registriesmonitor 对象。

  • 第 43 至 51 行:从 ApplicationConfig 对象中,读取 registriesmonitor 对象。

  • 第 52 至 57 行:泛化接口的实现。

  • 第 58 至 70 行:普通接口的实现。

    • 第 60 至 64 行:根据 interfaceName ,获得对应的接口类,并赋值给 interfaceClass

    • 第 66 行:调用 #checkInterfaceAndMethods(interfaceClass, methods) 方法,检查接口和方法。

      • 🙂 本文有已经有这个方法的解析。

    • 第 68 行:调用 #checkRef() 方法,校验指向的 Service 对象。

    • 第 69 行:标记 generic泛化实现。

  • 第 71 至 86 行:处理服务接口客户端本地代理( local )相关。实际目前已经废弃,此处主要用于兼容,使用 stub 属性,参见 AbstractInterfaceConfig#setLocal(local) 方法的注释说明

  • 第 87 至 102 行:处理服务接口客户端本地代理( stub 属性,参见 AbstractInterfaceConfig#setLocal(local) )相关。

  • 第 104 行:调用 #checkApplication() 方法,校验 ApplicationConfig 配置。

    • 🙂 直接点击方法查看,较为简单,已经添加详细注释。

  • 第 106 行:调用 #checkRegistry() 方法,校验 RegistryConfig 配置。

    • 🙂 直接点击方法查看,较为简单,已经添加详细注释。

  • 第 108 行:调用 #checkProtocol() 方法,校验 ProtocolConfig 配置数组。

    • 🙂 直接点击方法查看,较为简单,已经添加详细注释。

  • 第 110 行:调用 #appendProperties(config) 方法,读取属性配置( 环境变量 + properties 属性 )到 ServiceConfig 对象(自己)。

  • 第 112 行:调用 #checkStubAndMock(interfaceClass) 方法,校验 Stub 和 Mock 相关的配置。

  • 第 113 至 116 行:服务路径 path 为空时,缺省为接口名。

  • 第 118 行:调用 #doExportUrls() 方法,暴露服务。此方法包含了我们上述的 3+4 部分。

  • 第 119 至 121 行:// TODO ,等待 qos

因为本文不分享 4 部分,所以下面我们只看 #doExportUrls() 方法中,调用 #doExportUrlsFor1Protocol(protocolConfig, registryURLs) 方法,和 3 有关的部分。代码如下:

 1: private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
  2:     // 协议名
  3:     String name = protocolConfig.getName();
  4:     if (name == null || name.length() == 0) {
  5:         name = "dubbo";
  6:     }
  7: 
  8:     // 将 `side`,`dubbo`,`timestamp`,`pid` 参数,添加到 `map` 集合中。
  9:     Map<String, String> map = new HashMap<String, String>();
 10:     map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
 11:     map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
 12:     map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
 13:     if (ConfigUtils.getPid() > 0) {
 14:         map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
 15:     }
 16:     // 将各种配置对象,添加到 `map` 集合中。
 17:     appendParameters(map, application);
 18:     appendParameters(map, module);
 19:     appendParameters(map, provider, Constants.DEFAULT_KEY); // ProviderConfig ,为 ServiceConfig 的默认属性,因此添加 `default` 属性前缀。
 20:     appendParameters(map, protocolConfig);
 21:     appendParameters(map, this);
 22:     // 将 MethodConfig 对象数组,添加到 `map` 集合中。
 23:     if (methods != null && !methods.isEmpty()) {
 24:         for (MethodConfig method : methods) {
 25:             // 将 MethodConfig 对象,添加到 `map` 集合中。
 26:             appendParameters(map, method, method.getName());
 27:             // 当 配置了 `MethodConfig.retry = false` 时,强制禁用重试
 28:             String retryKey = method.getName() + ".retry";
 29:             if (map.containsKey(retryKey)) {
 30:                 String retryValue = map.remove(retryKey);
 31:                 if ("false".equals(retryValue)) {
 32:                     map.put(method.getName() + ".retries", "0");
 33:                 }
 34:             }
 35:             // 将 ArgumentConfig 对象数组,添加到 `map` 集合中。
 36:             List<ArgumentConfig> arguments = method.getArguments();
 37:             if (arguments != null && !arguments.isEmpty()) {
 38:                 for (ArgumentConfig argument : arguments) {
 39:                     // convert argument type
 40:                     if (argument.getType() != null && argument.getType().length() > 0) { // 指定了类型
 41:                         Method[] methods = interfaceClass.getMethods();
 42:                         // visit all methods
 43:                         if (methods != null && methods.length > 0) {
 44:                             for (int i = 0; i < methods.length; i++) {
 45:                                 String methodName = methods[i].getName();
 46:                                 // target the method, and get its signature
 47:                                 if (methodName.equals(method.getName())) { // 找到指定方法
 48:                                     Class<?>[] argTypes = methods[i].getParameterTypes();
 49:                                     // one callback in the method
 50:                                     if (argument.getIndex() != -1) { // 指定单个参数的位置 + 类型
 51:                                         if (argTypes[argument.getIndex()].getName().equals(argument.getType())) {
 52:                                             // 将 ArgumentConfig 对象,添加到 `map` 集合中。
 53:                                             appendParameters(map, argument, method.getName() + "." + argument.getIndex()); // `${methodName}.${index}`
 54:                                         } else {
 55:                                             throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
 56:                                         }
 57:                                     } else {
 58:                                         // multiple callbacks in the method
 59:                                         for (int j = 0; j < argTypes.length; j++) {
 60:                                             Class<?> argClazz = argTypes[j];
 61:                                             if (argClazz.getName().equals(argument.getType())) {
 62:                                                 // 将 ArgumentConfig 对象,添加到 `map` 集合中。
 63:                                                 appendParameters(map, argument, method.getName() + "." + j); // `${methodName}.${index}`
 64:                                                 if (argument.getIndex() != -1 && argument.getIndex() != j) { // 多余的判断,因为 `argument.getIndex() == -1` 。
 65:                                                     throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
 66:                                                 }
 67:                                             }
 68:                                         }
 69:                                     }
 70:                                 }
 71:                             }
 72:                         }
 73:                     } else if (argument.getIndex() != -1) { // 指定单个参数的位置
 74:                         // 将 ArgumentConfig 对象,添加到 `map` 集合中。
 75:                         appendParameters(map, argument, method.getName() + "." + argument.getIndex()); // `${methodName}.${index}`
 76:                     } else {
 77:                         throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
 78:                     }
 79: 
 80:                 }
 81:             }
 82:         } // end of methods for
 83:     }
 84: 
 85:     // generic、methods、revision
 86:     if (ProtocolUtils.isGeneric(generic)) {
 87:         map.put("generic", generic);
 88:         map.put("methods", Constants.ANY_VALUE);
 89:     } else {
 90:         String revision = Version.getVersion(interfaceClass, version);
 91:         if (revision != null && revision.length() > 0) {
 92:             map.put("revision", revision); // 修订号
 93:         }
 94: 
 95:         String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); // 获得方法数组
 96:         if (methods.length == 0) {
 97:             logger.warn("NO method found in service interface " + interfaceClass.getName());
 98:             map.put("methods", Constants.ANY_VALUE);
 99:         } else {
100:             map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
101:         }
102:     }
103:     // token ,参见《令牌校验》http://dubbo.apache.org/zh-cn/docs/user/demos/token-authorization.html
104:     if (!ConfigUtils.isEmpty(token)) {
105:         if (ConfigUtils.isDefault(token)) {
106:             map.put("token", UUID.randomUUID().toString());
107:         } else {
108:             map.put("token", token);
109:         }
110:     }
111:     // 协议为 injvm 时,不注册,不通知。
112:     if ("injvm".equals(protocolConfig.getName())) {
113:         protocolConfig.setRegister(false);
114:         map.put("notify", "false");
115:     }
116:     // export service
117:     String contextPath = protocolConfig.getContextpath();
118:     if ((contextPath == null || contextPath.length() == 0) && provider != null) {
119:         contextPath = provider.getContextpath();
120:     }
121: 
122:     // host、port
123:     String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
124:     Integer port = this.findConfigedPorts(protocolConfig, name, map);
125: 
126:     // 创建 Dubbo URL 对象
127:     URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
128: 
129:     // 配置规则,参见《配置规则》http://dubbo.apache.org/zh-cn/docs/user/demos/config-rule.html
130:     if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
131:             .hasExtension(url.getProtocol())) {
132:         url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
133:                 .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
134:     }
135: 
136:     // 省略【服务暴露】逻辑
137: }
  • 第 2 至 6 行:协议名空时,缺省 "dubbo"

  • 第 9 行:创建参数集合 map ,用于下面创建 Dubbo URL 的 parameters 属性。

  • 第 10 至 15 行:将 side dubbo timestamp timestamp pid 添加到 map 中。

  • 第 16 至 21 行:调用 #appendParameters(map, config) 方法,将各种配置对象添加到 map 中。

  • 第 22 至 83 行:调用 MethodConfig 对象数组,添加到 map 中。

    • 目的是将每个 MethodConfig 和其对应的 ArgumentConfig 对象数组,添加到 map 中。

    • 🙂 代码比较冗长,胖友耐心看注释,建议进行调试每种情况。

  • 第 85 至 102 行:将 generic methods revisionmap 中。

  • 第 103 至 110 行:将 token 添加到 map 中。

  • 第 111 至 115 行:当协议为 injvm 时,添加 notify = falsemap 中,表示不注册,不通知。

  • 第 116 至 120 行:获得 contextPath ,基础路径,即java web应用中常说的context path 。

  • 第 123 行:调用 #this.findConfigedHosts(protocolConfig, registryURLs, map) 方法,获得注册到注册中心的服务提供者 Host 。

  • 第 124 行:调用 #findConfigedHosts(protocolConfig, name, map) 方法,获得注册到注册中心的服务提供者 Port 。

    • 🙂 代码比较冗长,胖友耐心看注释,建议进行调试每种情况。

  • 第 127 行:创建 Dubbo URL 。

  • 第 129 至 134 行:配置规则,后续详细解析。

  • 第 136 行:省略【服务暴露】逻辑。

9. 为什么继承???

我们以 ServiceConfig 和 ProviderConfig 来举例子,两者都继承 AbstractServiceConfig。
从属性上,两者有相同的属性,例如 group / version
同时,也存在着一些差异,例如 ServiceConfig.interfaceName / ProviderConfig.host

另外,我们在看看 ServiceConfig 和 MethodConfig ,两者都继承 AbstractMethodConfig。
在 ServiceConfig 中,可以配置下属所有方法的 retries 次数,也可以在 MethodConfig 中自定义 retries 次数。

通过继承,获得相同的属性。

10. Version

Version#getVersion(cls, defaultVersion) 方法,获得版本号。代码如下:

 1: public static String getVersion(Class<?> cls, String defaultVersion) {
 2:     try {
 3:         // find version info from MANIFEST.MF first
 4:         String version = cls.getPackage().getImplementationVersion();
 5:         if (version == null || version.length() == 0) {
 6:             version = cls.getPackage().getSpecificationVersion();
 7:         }
 8:         if (version == null || version.length() == 0) {
 9:             // guess version fro jar file name if nothing's found from MANIFEST.MF
10:             CodeSource codeSource = cls.getProtectionDomain().getCodeSource();
11:             if (codeSource == null) {
12:                 logger.info("No codeSource for class " + cls.getName() + " when getVersion, use default version " + defaultVersion);
13:             } else {
14:                 String file = codeSource.getLocation().getFile();
15:                 if (file != null && file.length() > 0 && file.endsWith(".jar")) {
16:                     file = file.substring(0, file.length() - 4);
17:                     int i = file.lastIndexOf('/');
18:                     if (i >= 0) {
19:                         file = file.substring(i + 1);
20:                     }
21:                     i = file.indexOf("-");
22:                     if (i >= 0) {
23:                         file = file.substring(i + 1);
24:                     }
25:                     while (file.length() > 0 && !Character.isDigit(file.charAt(0))) {
26:                         i = file.indexOf("-");
27:                         if (i >= 0) {
28:                             file = file.substring(i + 1);
29:                         } else {
30:                             break;
31:                         }
32:                     }
33:                     version = file;
34:                 }
35:             }
36:         }
37:         // return default version if no version info is found
38:         return version == null || version.length() == 0 ? defaultVersion : version;
39:     } catch (Throwable e) {
40:         // return default version when any exception is thrown
41:         logger.error("return default version, ignore exception " + e.getMessage(), e);
42:         return defaultVersion;
43:     }
44: }

第 3 至 7 行:从 MAINFEST.MF 中获得版本号。以 spring-boot-starter-1.5.10.RELEASE.jar 举例子:

  • 第 8 至 36 行:若获取不到,从 jar 包命名可能带的版本号作为结果。例如上面的例子,1.5.10.RELEASE

  • 第 38 行:返回版本号。若不存在,返回默认版本号。