SkyWalking Java Agent 配置初始化流程分析
上一篇文章我们通过 SkyWalking Java Agent 日志组件分析一文详细介绍了日志相关的底层实现原理,今天我们要正式进入 premain 方法了,
premain 方法见名知义就是在我们 Java 程序的 main 方法之前运行的方法,一般我们通过 JVM 参数 -javaagent:/path/to/skywalking-agent.jar 的方式指定代理程序。
java agent 相关内容不是本文重点,更多内容请参考文末相关阅读连接。
1 | /** |
今天我们要重点分析的就是这行代码的内部实现1
SnifferConfigInitializer.initializeCoreConfig(agentArgs);
SnifferConfigInitializer 类使用多种方式初始化配置,内部实现有以下几个重要步骤:
loadConfig() 加载配置文件
replacePlaceholders() 解析 placeholder
overrideConfigBySystemProp() 读取 System.Properties 属性
overrideConfigByAgentOptions() 解析 agentArgs 参数配置
initializeConfig() 将以上读取到的配置信息映射到 Config 类的静态属性
configureLogger() 根据配置的 Config.Logging.RESOLVER 重配置 Log,更多关于日志参见文章
验证非空参数 agent.service_name 和 collector.servers。
- 从指定的配置文件路径读取配置文件内容,通过 -Dskywalking_config=/xxx/yyy 指定配置文件位置;
- 如果没有指定配置文件路径,则从默认配置文件 config/agent.config 读取;
将配置文件内容加载到 Properties;
从系统属性读取配置覆盖配置;
解析 agentArgs 覆盖配置;
将配置信息映射到 Config 类的静态属性;
必填参数验证。
定位 skywalking-agent.jar 所在目录
读取默认配置文件,以及后面加载插件都需要用到 SkyWalking agent.jar 所在目录
skywalking-agent 目录结构如下:
config 目录存放的是 agent.config 配置文件
1 | /** |
new File(AgentPackagePath.getPath(), DEFAULT_CONFIG_FILE_NAME) 定位默认配置文件的位置,
AgentPackagePath.getPath() 方法用来获取 skywalking-agent.jar 所在目录,其中涉及到一些的字符串操作,大家对这部分感兴趣的可以看源码研究。
1 | /** |
配置优先级 Setting-override.md
Agent Options > System.Properties(-D) > System environment variables > Config file
System.getProperties() 和 System.getenv() 区别
System.getProperties() 获取 Java 虚拟机相关的系统属性(比如 java.version、 java.io.tmpdir 等),通过 java -D配置;
System.getenv() 获取系统环境变量(比如 JAVA_HOME、Path 等),通过操作系统配置。
请参考 https://www.cnblogs.com/clarke157/p/6609761.html
将配置信息映射到 Config 类
在我们的日常开发中一般是直接从 Properties 读取需要的配置项,SkyWalking Java Agent 并没有这么做,而是定义一个配置类 Config,将配置项映射到 Config 类的静态属性中,
其他地方需要配置项的时候,直接从类的静态属性获取就可以了,非常方便使用。
ConfigInitializer 就是负责将 Properties 中的 key/value 键值对映射到类(比如 Config 类)的静态属性,其中 key 对应类的静态属性,value 赋值给静态属性的值。
1 | /** |
比如通过 agent.config 配置文件配置服务名称1
2# The service name in UI
agent.service_name=${SW_AGENT_NAME:Your_ApplicationName}
agent 对应 Config 类的静态内部类 Agent
service_name 对应静态内部类 Agent 的静态属性 SERVICE_NAME
SkyWalking Java Agent 在这里面使用了下划线而不是驼峰来命名配置项,将类的静态属性名称转换成下划线配置名称非常方便,直接转成小写就可以通过 Properties 获取对应的值了。
ConfigInitializer.initNextLevel 方法涉及到的技术点有反射、递归调用、栈等,更多实现细节参考 ConfigInitializer 类源码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/**
* Init a class's static fields by a {@link Properties}, including static fields and static inner classes.
* <p>
*/
public class ConfigInitializer {
public static void initialize(Properties properties, Class<?> rootConfigType) throws IllegalAccessException {
initNextLevel(properties, rootConfigType, new ConfigDesc());
}
private static void initNextLevel(Properties properties, Class<?> recentConfigType,
ConfigDesc parentDesc) throws IllegalArgumentException, IllegalAccessException {
for (Field field : recentConfigType.getFields()) {
if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) {
String configKey = (parentDesc + "." + field.getName()).toLowerCase();
Class<?> type = field.getType();
if (type.equals(Map.class)) {
/*
* Map config format is, config_key[map_key]=map_value
* Such as plugin.opgroup.resttemplate.rule[abc]=/url/path
*/
// Deduct two generic types of the map
ParameterizedType genericType = (ParameterizedType) field.getGenericType();
Type[] argumentTypes = genericType.getActualTypeArguments();
Type keyType = null;
Type valueType = null;
if (argumentTypes != null && argumentTypes.length == 2) {
// Get key type and value type of the map
keyType = argumentTypes[0];
valueType = argumentTypes[1];
}
Map map = (Map) field.get(null);
// Set the map from config key and properties
setForMapType(configKey, map, properties, keyType, valueType);
} else {
/*
* Others typical field type
*/
String value = properties.getProperty(configKey);
// Convert the value into real type
final Length lengthDefine = field.getAnnotation(Length.class);
if (lengthDefine != null) {
if (value != null && value.length() > lengthDefine.value()) {
value = value.substring(0, lengthDefine.value());
}
}
Object convertedValue = convertToTypicalType(type, value);
if (convertedValue != null) {
// 通过反射给静态属性设置值
field.set(null, convertedValue);
}
}
}
}
// recentConfigType.getClasses() 获取 public 的 classes 和 interfaces
for (Class<?> innerConfiguration : recentConfigType.getClasses()) {
// parentDesc 将类(接口)名入栈
parentDesc.append(innerConfiguration.getSimpleName());
// 递归调用
initNextLevel(properties, innerConfiguration, parentDesc);
// parentDesc 将类(接口)名出栈
parentDesc.removeLastDesc();
}
}
// 省略部分代码....
}
class ConfigDesc {
private LinkedList<String> descs = new LinkedList<>();
void append(String currentDesc) {
if (StringUtil.isNotEmpty(currentDesc)) {
descs.addLast(currentDesc);
}
}
void removeLastDesc() {
descs.removeLast();
}
public String toString() {
return String.join(".", descs);
}
}
阅读源码过程对部分代码添加的注释已经提交到 GitHub 上了,我就不在此贴太多代码了,具体参见 https://github.com/geekymv/skywalking-java/tree/v8.8.0-annotated