引子
在 “事件處理業務的簡易組件編排框架” 中,講到了事件處理流程的組件編排,是通過枚舉來實現事件處理流程的配置的。實際應用中,可以采用 YAML 文件來配置。
YAML 是一種主流的配置文件語法。相比 XML,形式上更為簡潔;相比 JSON, 去掉了引號之繁瑣;相比扁平的 properties,能夠表達更復雜的結構。本文來學習 YAML 語法和解析。
基礎知識
- 大小寫敏感
- YAML 通過縮進來標識數據或屬性之間的層級;
- 縮進時不允許使用Tab鍵,只允許使用空格;
- 同一縮進的屬性,是上一層的同一對象的平級屬性;
- 數組元素,以“縮進 - ” 表示;
- # 表示注釋,從這個字符一直到行尾,都會被解析器忽略。
YAML 支持的數據結構有三種:
- 對象:鍵值對的集合,又稱為映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 數組:一組按次序排列的值,又稱為序列(sequence) / 列表(list)
- 純量(scalars):單個的、不可再分的值
可參考: “YAML語法介紹”
基本結構
如下所示,是一個典型的 YML 配置(eventflow_finished.yml):
bizTypes:
- 'docker_bounce_shell'
eventType: 'create'
eventSourceType: 'bizEvent'
eventflowClassName: 'cc.lovesq.eventflow.demo.DefaultEventFlow'
model: 'eventflow'
way: 'serial'
originParamType: 'cc.lovesq.eventflow.demo.DefaultEventData'
componentParamType: 'cc.lovesq.eventflow.demo.DefaultFlowContext'
components:
- 'notificationSender_common'
- 'defaultBigDataSender'
- 'cc.lovesq.eventflow.demo.components.DefaultBigDataSender'
要解析這個配置,需要建立相應的對象模型,就像將 JSON 字符串轉換成 Java 對象一樣。這個對象 EventFlowExecutionModel 也不難建立:
public class EventFlowExecutionModel extends ComponentsExecutionModel {
/**
* 事件流程處理的業務類型,比如反彈shell, 本地提權等
* 由於一個通用事件處理流程可以處理多個業務類型,因此這里可以是列表
*/
private List<String> bizTypes;
/** 事件類型,比如創建,更新等 */
private String eventType;
/** 事件流程處理的事件源類型,比如 agent上報事件, bizEvent事件 */
private String eventSourceType;
/** 根據類名來選擇事件處理流程實例 */
private String eventflowClassName;
/** eventflow 事件流程處理(不指定默認) components 有序組件集執行 */
private ExecutionModel model = ExecutionModel.eventflow;
/** 原始傳入事件處理流程的入口參數類型 */
private String originParamType;
/** serial 串行(不指定默認) parallel 並發 */
protected ExecWay way = ExecWay.serial;
/** 組件參數類型 */
protected String componentParamType;
protected List<String> components;
// getter/ setter
}
使用 snakeyaml 解析:
public class ConfigUtil {
/**
* 通過 yml 文件加載組件執行模型對象
*/
public static EventFlowExecutionModel load(String ymlFilePath) {
return load(ymlFilePath, EventFlowExecutionModel.class);
}
/**
* 通過 yml 文件加載對象模型
*/
public static <T> T load(String ymlFilePath, Class<T> cls) {
Yaml yaml = new Yaml();
T model = yaml.loadAs(cls.getClassLoader().getResourceAsStream(ymlFilePath), cls);
return model;
}
}
需要引入依賴:
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.27</version>
</dependency>
編寫單測:
public class ConfigUtilTest extends CommonForAssert {
@Test
public void testLoad() {
EventFlowExecutionModel eventFlowExecModel = ConfigUtil.load("eventflow_finished.yml");
eq(Arrays.asList("docker_bounce_shell"), eventFlowExecModel.getBizTypes());
eq("cc.lovesq.eventflow.demo.DefaultEventFlow", eventFlowExecModel.getEventflowClassName());
eq(ExecutionModel.eventflow, eventFlowExecModel.getModel());
eq(ExecWay.serial, eventFlowExecModel.getWay());
eq("cc.lovesq.eventflow.demo.DefaultEventData", eventFlowExecModel.getOriginParamType());
eq("cc.lovesq.eventflow.demo.DefaultFlowContext", eventFlowExecModel.getComponentParamType());
eq(Arrays.asList("notificationSender_common", "defaultBigDataSender", "cc.lovesq.eventflow.demo.components.DefaultBigDataSender"), eventFlowExecModel.getComponents());
}
}
支持多個事件處理流程
實際應用中,顯然需要在一個文件里支持多個事件處理流程。 如下所示(eventflow.yml):
version: 1.0
flows:
- bizTypes:
- 'docker_bounce_shell'
- 'abnormal_login'
eventType: 'create'
eventSourceType: 'bizEvent'
eventflowClassName: 'cc.lovesq.eventflow.demo.DefaultEventFlow'
model: 'eventflow'
way: 'serial'
originParamType: 'cc.lovesq.eventflow.demo.DefaultEventData'
componentParamType: 'cc.lovesq.eventflow.demo.DefaultFlowContext'
components:
- 'notificationSender_common'
- 'defaultBigDataSender'
- 'cc.lovesq.eventflow.demo.components.DefaultBigDataSender'
- bizTypes:
- 'local_rights'
- 'docker_local_rights'
eventType: 'create'
eventSourceType: 'bizEvent'
eventflowClassName: 'cc.lovesq.eventflow.demo.DefaultEventFlow'
model: 'eventflow'
way: 'serial'
originParamType: 'cc.lovesq.eventflow.demo.DefaultEventData'
componentParamType: 'cc.lovesq.eventflow.demo.DefaultFlowContext'
components:
- 'notificationSender_common'
- 'defaultBigDataSender'
相應的,也要建立對應的 Java 對象模型:
public class EventFlowsModel {
private String version;
private List<EventFlowExecutionModel> flows;
// getter / setter
}
錨點與引用
在配置文件 eventflow.yml 中,除了 bizTypes 和 components ,中間的基本都是重復配置。yaml 支持通過錨點 &config 和引用 <<: *config 來引用重復配置。如下所示(eventflow_brief.yml):
version: 1.0
flows:
- bizTypes:
- 'docker_bounce_shell'
- 'abnormal_login'
commonConfig: &commonConfig
eventType: 'create'
eventSourceType: 'bizEvent'
eventflowClassName: 'cc.lovesq.eventflow.demo.DefaultEventFlow'
model: 'eventflow'
way: 'serial'
originParamType: 'cc.lovesq.eventflow.demo.DefaultEventData'
componentParamType: 'cc.lovesq.eventflow.demo.DefaultFlowContext'
components:
- 'notificationSender_common'
- 'defaultBigDataSender'
- 'cc.lovesq.eventflow.demo.components.DefaultBigDataSender'
- bizTypes:
- 'local_rights'
- 'docker_local_rights'
commonConfig:
<<: *commonConfig
components:
- 'notificationSender_common'
- 'defaultBigDataSender'
對應的 Java 對象為:
public class CommonConfig {
/** 事件類型,比如創建,更新等 */
private String eventType;
/** 事件流程處理的事件源類型,比如 agent上報事件, bizEvent事件 */
private String eventSourceType;
/** 根據類名來選擇事件處理流程實例 */
private String eventflowClassName;
/** eventflow 事件流程處理(不指定默認) components 有序組件集執行 */
private ExecutionModel model = ExecutionModel.eventflow;
/** 原始傳入事件處理流程的入口參數類型 */
private String originParamType;
/** serial 串行(不指定默認) parallel 並發 */
protected ExecWay way = ExecWay.serial;
/** 組件參數類型 */
protected String componentParamType;
// getter / setter
}
public class EventFlowExecutionModelRefCommon {
private List<String> bizTypes;
private CommonConfig commonConfig;
private List<String> components;
// getter / setter
}
public class EventFlowsModelRefCommon {
private String version;
private List<EventFlowExecutionModelRefCommon> flows;
// getter / setter
}
這里的訣竅就是:建立與 yml 文件里對應的模型,就像 JSON 對象解析類似。