API配置(二)之服务提供者
本文基于 Dubbo 2.6.1 版本,望知悉。
友情提示,【配置】这块的内容,会相对比较枯燥。所以,如果看到一些很难懂的地方,建议先跳过。
对于 Dubbo ,重点是要去理解,多协议、RPC、容错等等模块,而不是【配置】。
估计好多胖友被【配置】这章劝退了把???
1. 概述
本文接 《API 配置(一)之应用》 ,分享服务提供者相关的配置:包括 provider-config 和 sub-config 部分。
- 黄框部分,provider-side
- 其他部分,sub-config
还是老样子,我们先来看一段 《Dubbo 用户指南 —— API 配置》 ,服务提供者的初始化代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 服务实现
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 ,服务提供者协议配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:protocol》 文档。
3. AbstractMethodConfig
com.alibaba.dubbo.config.AbstractMethodConfig ,方法级配置的抽象类。
- 部分属性的解释,参见 《Dubbo 用户指南 —— dubbo:method》 文档。
4. MethodConfig
com.alibaba.dubbo.config.MethodConfig ,继承 AbstractMethodConfig ,方法级配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:method》 文档。
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 ,抽象服务配置类。
- 具体属性的解释,需要寻找在 《Dubbo 用户指南 —— dubbo:service》 或 《Dubbo 用户指南 —— dubbo:provider》 文档。
7. ProviderConfig
com.alibaba.dubbo.config.ProviderConfig ,实现 AbstractServiceConfig ,服务提供者缺省值配置。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:provider》 文档。
8. ServiceConfig
com.alibaba.dubbo.config.ServiceConfig ,服务提供者暴露服务配置类。
- 具体属性的解释,参见 《Dubbo 用户指南 —— dubbo:service》 文档。
下面,我们进入正戏。
在文初的 ServiceConfig 的初始化示例代码中,最后调用的是 ServiceConfig#export() 方法。从方法的命名,我们可以看出,暴露服务。该方法主要做了如下几件事情:
- 进一步初始化 ServiceConfig 对象。
- 校验 ServiceConfig 对象的配置项。
- 使用 ServiceConfig 对象,生成数组 Dubbo URL 对象。
- 使用 Dubbo URL 对象,暴露服务。
本文重点在服务提供者相关的配置,因此只解析 1+2+3 部分( 不包括 4 )。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public synchronized void export() {
// 当 export 或者 delay 未配置,从 ProviderConfig 对象读取。
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
// 不暴露服务( export = false ) ,则不进行暴露服务逻辑。
if (export != null && !export) {
return;
}
// 延迟暴露
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
// 立即暴露
} else {
doExport();
}
}
- 第 2 至 10 行:当 export 或 delay 未配置时,从 ProviderConfig 对象读取。
- 第 11 至 14 行:当配置不需要暴露服务(
export = false)时,直接返回。 - 第 17 至 22 行:当配置延迟暴露(
delay > 0)时,使用 delayExportExecutor 调度,调用#doExport()方法。 - 第 23 至 26 行:立即暴露,调用
#doExport()方法。
#doExport() 方法,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
protected synchronized void doExport() {
// 检查是否可以暴露,若可以,标记已经暴露。
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
// 校验接口名非空
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
// 拼接属性配置(环境变量 + properties 属性)到 ProviderConfig 对象
checkDefault();
// 从 ProviderConfig 对象中,读取 application、module、registries、monitor、protocols 配置对象。
if (provider != null) {
if (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {
registries = provider.getRegistries();
}
if (monitor == null) {
monitor = provider.getMonitor();
}
if (protocols == null) {
protocols = provider.getProtocols();
}
}
// 从 ModuleConfig 对象中,读取 registries、monitor 配置对象。
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
// 从 ApplicationConfig 对象中,读取 registries、monitor 配置对象。
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
// 泛化接口的实现
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
// 普通接口的实现
} else {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// 校验接口和方法
checkInterfaceAndMethods(interfaceClass, methods);
// 校验指向的 service 对象
checkRef();
generic = Boolean.FALSE.toString();
}
// 处理服务接口客户端本地代理( `local` )相关。实际目前已经废弃,使用 `stub` 属性,
// 参见 `AbstractInterfaceConfig#setLocal` 方法。
if (local != null) {
// 设为 true,表示使用缺省代理类名,即:接口名 + Local 后缀
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
try {
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName()
+ " not implement interface " + interfaceName);
}
}
// 处理服务接口客户端本地代理( `stub` )相关
if (stub != null) {
// 设为 true,表示使用缺省代理类名,即:接口名 + Stub 后缀
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName()
+ " not implement interface " + interfaceName);
}
}
// 校验 ApplicationConfig 配置。
checkApplication();
// 校验 RegistryConfig 配置。
checkRegistry();
// 校验 ProtocolConfig 配置数组。
checkProtocol();
// 读取环境变量和 properties 配置到 ServiceConfig 对象。
appendProperties(this);
// 校验 Stub 和 Mock 相关的配置
checkStubAndMock(interfaceClass);
// 服务路径,缺省为接口名
if (path == null || path.length() == 0) {
path = interfaceName;
}
// 暴露服务
doExportUrls();
// TODO 芋艿,等待 qos
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
- 第 2 至 9 行:检查是否可以暴露。若可以,标记已经暴露(
exported = true)。 - 第 10 至 13 行:校验接口名 interfaceName 非空。
- 第 15 行:调用 #checkDefault() 方法,读取属性配置 ( 环境变量 + properties 属性 )到 ProviderConfig 对象。
- 关于”属性配置” ,在 《精尽 Dubbo 源码解析 —— 属性配置》 详细解析。
- 直接点击方法查看,较为简单,已经添加详细注释。
- 第 16 至 33 行:从 ProviderConfig 对象中,读取 application 、module 、registries 、monitor 、protocols 对象。
- 第 34 至 42 行:从 ModuleConfig 对象中,读取 registries 、monitor 对象。
- 第 43 至 51 行:从 ApplicationConfig 对象中,读取 registries 、monitor 对象。
- 第 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 协议名
String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
name = "dubbo";
}
// 将 `side`,`dubbo`,`timestamp`,`pid` 参数,添加到 `map` 集合中。
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 将各种配置对象,添加到 `map` 合中。
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY); // ProviderConfig ,为 ServiceConfig 的默认属性,因此添加 `default` 属性前缀。
appendParameters(map, protocolConfig);
appendParameters(map, this);
// 将 MethodConfig 对象数组,添加到 `map` 集合中。
if (methods != null && !methods.isEmpty()) {
for (MethodConfig method : methods) {
// 将 MethodConfig 对象,添加到 `map` 集合中。
appendParameters(map, method, method.getName());
// 当 配置了 `MethodConfig.retry = false` 时,强制禁用重试
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
// 将 ArgumentConfig 对象数组,添加到 `map` 集合中。
List<ArgumentConfig> arguments = method.getArguments();
if (arguments != null && !arguments.isEmpty()) {
for (ArgumentConfig argument : arguments) {
// convert argument type
if (argument.getType() != null && argument.getType().length() > 0) { // 指定了类型
Method[] methods = interfaceClass.getMethods();
// visit all methods
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// target the method, and get its signature
if (methodName.equals(method.getName())) { // 找到指定方法
Class<?>[] argTypes = methods[i].getParameterTypes();
// one callback in the method
if (argument.getIndex() != -1) { // 指定单个参数的位置 + 类型
if (argTypes[argument.getIndex()].getName().equals(argument.getType())) {
// 将 ArgumentConfig 对象,添加到 `map` 合中。
appendParameters(map, argument, method.getName() + "." + argument.getIndex()); // `${methodName}.${index}`
} else {
throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
} else {
// multiple callbacks in the method
for (int j = 0; j < argTypes.length; j++) {
Class<?> argClazz = argTypes[j];
if (argClazz.getName().equals(argument.getType())) {
// 将 ArgumentConfig 对象,添加到 `map` 集合中。
appendParameters(map, argument, method.getName() + "." + j); // `${methodName}.${index}`
if (argument.getIndex() != -1 && argument.getIndex() != j) { // 多余的判断,因为 `argument.getIndex() == -1` 。
throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
}
}
}
}
}
}
}
} else if (argument.getIndex() != -1) { // 指定单个参数的位置
// 将 ArgumentConfig 对象,添加到 `map` 集合中。
appendParameters(map, argument, method.getName() + "." + argument.getIndex()); // `${methodName}.${index}`
} else {
throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
}
}
}
} // end of methods for
}
// generic、methods、revision
if (ProtocolUtils.isGeneric(generic)) {
map.put("generic", generic);
map.put("methods", Constants.ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision); // 修订号
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); // 获得方法数组
if (methods.length == 0) {
logger.warn("NO method found in service interface " + interfaceClass.getName());
map.put("methods", Constants.ANY_VALUE);
} else {
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
// token ,参见《令牌校验》http://dubbo.apache.org/zh-cn/docs/user/demos/token-authorization.html
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
map.put("token", UUID.randomUUID().toString());
} else {
map.put("token", token);
}
}
// 协议为 injvm 时,不注册,不通知。
if ("injvm".equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
// export service
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
// host、port
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
// 创建 Dubbo URL 对象
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
// 配置规则,参见《配置规则》http://dubbo.apache.org/zh-cn/docs/user/demos/config-rule.html
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
// 省略【服务暴露】逻辑
}
- 第 2 至 6 行:协议名空时,缺省 “dubbo”。
- 第 9 行:创建参数集合 map,用于下面创建 Dubbo URL 的 parameters 属性。
- 第 10 至 15 行:将
side、dubbo、timestamp、pid添加到map中。 - 第 16 至 21 行:调用
#appendParameters(map, config)方法,将各种配置对象添加到map中。#appendParameters(map, config)方法,在 《API 配置(一)之应用》
- 第 22 至 83 行:调用 MethodConfig 对象数组,添加到
map中。- 目的是将每个 MethodConfig 和其对应的 ArgumentConfig 对象数组,添加到
map中。 - 代码比较冗长,胖友耐心看注释,建议进行调试每种情况。
- 目的是将每个 MethodConfig 和其对应的 ArgumentConfig 对象数组,添加到
- 第 85 至 102 行:将
generic、methods、revision到map中。- revision,可能比较难理解,在 「10. Version」 详细解析。
- 第 103 至 110 行:将
token添加到map中。 - 第 111 至 115 行:当协议为
injvm时,添加notify = false到map中,表示不注册,不通知。 - 第 116 至 120 行:获得
contextPath,基础路径,即java web应用中常说的context path。 - 第 123 行:调用 #this.findConfigedHosts(protocolConfig, registryURLs, map) 方法,获得注册到注册中心的服务提供者 Host。
- 《Dubbo 用户指南 —— 主机绑定》
- 《dubbo注册服务IP解析异常及IP解析源码分析》
- 指定服务注册地址,参见 dubbo-docker-sample 示例项目。
- 代码比较冗长,胖友耐心看注释,建议进行调试每种情况。
- 第 124 行:调用 #findConfigedPorts(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static String getVersion(Class<?> cls, String defaultVersion) {
try {
// find version info from MANIFEST.MF first
String version = cls.getPackage().getImplementationVersion();
if (version == null || version.length() == 0) {
version = cls.getPackage().getSpecificationVersion();
}
if (version == null || version.length() == 0) {
// guess version fro jar file name if nothing's found from MANIFEST.MF
CodeSource codeSource = cls.getProtectionDomain().getCodeSource();
if (codeSource == null) {
logger.info("No codeSource for class " + cls.getName() + " when getVersion, use default version " + defaultVersion);
} else {
String file = codeSource.getLocation().getFile();
if (file != null && file.length() > 0 && file.endsWith(".jar")) {
file = file.substring(0, file.length() - 4);
int i = file.lastIndexOf('/');
if (i >= 0) {
file = file.substring(i + 1);
}
i = file.indexOf("-");
if (i >= 0) {
file = file.substring(i + 1);
}
while (file.length() > 0 && !Character.isDigit(file.charAt(0))) {
i = file.indexOf("-");
if (i >= 0) {
file = file.substring(i + 1);
} else {
break;
}
}
version = file;
}
}
}
// return default version if no version info is found
return version == null || version.length() == 0 ? defaultVersion : version;
} catch (Throwable e) {
// return default version when any exception is thrown
logger.error("return default version, ignore exception " + e.getMessage(), e);
return defaultVersion;
}
}
- 第 3 至 7 行:从 MAINFEST.MF 中获得版本号。以 spring-boot-starter-1.5.10.RELEASE.jar 举例子:
- 第 8 至 36 行:若获取不到,从 jar 包命名可能中带的版本号作为结果。例如上面的例子,
1.5.10.RELEASE。 - 第 38 行:返回版本号。若不存在,返回默认版本号。

