前言:
最近工作中需要将Prometheus的yml格式配置文件反序列化为Java实体,试了下Jackson就可以很完美的满足这个需求,正好Spring中自带Jackson,所以就用Jackson实现了。
正文:
在反序列化过程中碰到一个问题就是Prometheus的rule_files中的rules数组中的rule可以是RecordingRule也可以是AlertingRule,这个正好对应于Java中的多态。
配置格式如下:
rules:
- record: <string>
expr: <string>
labels:
[ <labelname>: <labelvalue> ]
- alert: <string>
expr: <string>
[ for: <duration> | default = 0s ]
labels:
[ <labelname>: <tmpl_string> ]
annotations:
[ <labelname>: <tmpl_string> ]
Java实体代码如下:
public class PrometheusConfigRuleVo {
protected String expr;
protected Map<String, String> labels;
public Map<String, String> getLabels() {
return labels;
}
public String getExpr() {
return expr;
}
public void setLabels(Map<String, String> labels) {
this.labels = labels;
}
public void setExpr(String expr) {
this.expr = expr;
}
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@EqualsAndHashCode(callSuper = true)
@Data
public class PrometheusConfigAlertingRuleVo extends PrometheusConfigRuleVo {
@JsonProperty(value = "for")
private Optional<String> _for;
private String alert;
private Map<String, String> annotations;
}
@EqualsAndHashCode(callSuper = true)
@Data
public class PrometheusConfigRecordingRuleVo extends PrometheusConfigRuleVo {
private String record;
}
上述代码中用了Optional类型字段,这个在使用Jackson时候需要引入com.fasterxml.jackson.datatype:jackson-datatype-jdk8 这个依赖,然后在ObjectMapper对象中调用registerModule(new Jdk8Module())方法即可让Optional类型正常序列化为Optional包含的内容。
上述代码直接反序列化rules为List<PrometheusConfigRuleVo>会报错,网上搜索到的文章提供的方案大多是使用@JsonTypeInfo和@JsonSubTypes注解来解决,如果是自己生成的yml文件可以通过这两个注解解决,但是我这里是prometheus用到的yml文件,我不能给序列化以后的yml文件中添加额外的类配置,所以用上述注解行不通,下文提供一种我认为是完美的解决方案。
@JsonDeserialize(using = PrometheusConfigRuleVo.RuleDeserialize.class)
public class PrometheusConfigRuleVo {
protected String expr;
protected Map<String, String> labels;
public Map<String, String> getLabels() {
return labels;
}
public String getExpr() {
return expr;
}
public void setLabels(Map<String, String> labels) {
this.labels = labels;
}
public void setExpr(String expr) {
this.expr = expr;
}
static class RuleDeserialize extends StdDeserializer<PrometheusConfigRuleVo> {
protected RuleDeserialize() {
super(PrometheusConfigRuleVo.class);
}
@Override
public PrometheusConfigRuleVo deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
final ObjectMapper mapper = (ObjectMapper) p.getCodec();
final ObjectNode treeNode = mapper.readTree(p);
if (treeNode.has("record")) {
return mapper.readValue(treeNode.toString(), PrometheusConfigRecordingRuleVo.class);
} else if (treeNode.has("alert")) {
return mapper.readValue(treeNode.toString(), PrometheusConfigAlertingRuleVo.class);
} else {
throw new RuntimeException("Not support type.");
}
}
}
}
@EqualsAndHashCode(callSuper = true)
@Data
@JsonDeserialize(as = PrometheusConfigRecordingRuleVo.class)
public class PrometheusConfigRecordingRuleVo extends PrometheusConfigRuleVo {
private String record;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@EqualsAndHashCode(callSuper = true)
@Data
@JsonDeserialize(as = PrometheusConfigAlertingRuleVo.class)
public class PrometheusConfigAlertingRuleVo extends PrometheusConfigRuleVo {
@JsonProperty(value = "for")
private Optional<String> _for;
private String alert;
private Map<String, String> annotations;
}
可以看到和最初的代码的差别是多了@JsonDeserialize注解,然后自己实现父类的反序列化方法,其中含有解析道rule时候如果alert字段就反序列化为PrometheusConfigAlertingRuleVo对象,如果有record字段就反序列化为PrometheusConfigRecordingRuleVo对象。
尾声:
上述文章是Jackson的解决方案,网上看到过Gson的解决方案,没有试验,将链接记录一下,后续有机会用到可以实验一下。
感叹一下 这两天用Jackson发现是真的强大。