public class NacosSyntaxCheckListener implements ApplicationListener<ApplicationStartedEvent>, InitializingBean {
private NacosConfigManager nacosConfigManager;
private List<ConfigSyntaxErrorHandler> errorHandlerList;
private Map<String, ConfigSyntaxChecker> checkerMap;
private ExecutorService executor;
// 构造器注入依赖
public NacosSyntaxCheckListener(NacosConfigManager nacosConfigManager, List<ConfigSyntaxChecker> configSyntaxCheckerList, List<ConfigSyntaxErrorHandler> configSyntaxErrorHandlerList) {
this.nacosConfigManager = nacosConfigManager;
// 不同文件类型有不同的语法规则,这里根据文件扩展名来区分,通过List结构实现自动扩展
this.checkerMap = new HashMap<>();
Optional.ofNullable(configSyntaxCheckerList)
.orElse(new ArrayList<>())
.forEach(this::registerSyntaxChecker);
log.info("NacosSyntaxCheckListener constructor, checkerMap.size={}", this.checkerMap.size());
// 语法检查失败处理,通过List结构实现自动扩展
this.errorHandlerList = Optional.ofNullable(configSyntaxErrorHandlerList).orElse(new ArrayList<>());
log.info("NacosSyntaxCheckListener constructor, errorHandlerList.size={}", this.errorHandlerList.size());
this.executor = Executors.newSingleThreadExecutor();
}
// 同一种类型多种扩展名(比如.yml和.yaml是一种语法)支持
private void registerSyntaxChecker(ConfigSyntaxChecker configSyntaxChecker) {
Optional.ofNullable(configSyntaxChecker.supportExtension())
.orElse(new ArrayList<>())
.forEach(extension -> this.checkerMap.put(extension, configSyntaxChecker));
}
@Override
public void afterPropertiesSet() throws Exception {
Runtime.getRuntime().addShutdownHook(new Thread(() -> executor.shutdown()));
}
// 服务启动之后,获取所有配置,并添加监听器
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
com.alibaba.cloud.nacos.NacosPropertySourceRepository.getAll().forEach(this::addListener);
}
private void addListener(NacosPropertySource nacosPropertySource) {
log.info("NacosSyntaxCheckListener.addListener begin, dataId={}, group={}",
nacosPropertySource.getDataId(), nacosPropertySource.getGroup());
String extension = FilenameUtils.getExtension(nacosPropertySource.getDataId());
final ConfigSyntaxChecker checker = checkerMap.get(extension);
if (extension == null) {
log.info("NacosSyntaxCheckListener.addListener no checker support, dataId={}, group={}",
nacosPropertySource.getDataId(), nacosPropertySource.getGroup());
return;
}
try {
nacosConfigManager.getConfigService().addListener(nacosPropertySource.getDataId(), nacosPropertySource.getGroup(), new Listener() {
@Override
public Executor getExecutor() {
return executor;
}
@Override
public void receiveConfigInfo(String configInfo) {
try {
log.info("NacosSyntaxCheckListener.receiveConfigInfo, dataId={}, group={}, config={}",
nacosPropertySource.getDataId(), nacosPropertySource.getGroup(), configInfo);
// 语法检查
checker.check(configInfo);
log.info("NacosSyntaxCheckListener.receiveConfigInfo, check success, dataId={}, group={}",
nacosPropertySource.getDataId(), nacosPropertySource.getGroup());
} catch (Exception e) {
NacosConfigProperties nacosConfigProperties = nacosConfigManager.getNacosConfigProperties();
errorHandlerList.forEach(handler -> {
// 语法检查失败之后做失败处理,比如打日志报警,或者发送钉钉/企微等方式
handler.onError(nacosConfigProperties, nacosPropertySource, e.getMessage());
});
}
}
});
} catch (Exception e) {
log.error("NacosSyntaxCheckListener.addListener error, dataId={}, group={}",
nacosPropertySource.getDataId(), nacosPropertySource.getGroup(), e);
}
}
}
public interface ConfigSyntaxChecker {
// 支持的文件扩展名
Collection<String> supportExtension();
// 语法检测
void check(String content);
}
// 内置的yaml格式语法检查,其他格式语法检查留作扩展,这里并未实现
public class YamlConfigSyntaxChecker implements ConfigSyntaxChecker {
@Override
public Collection<String> supportExtension() {
return Sets.newHashSet("yml", "yaml");
}
@Override
public void check(String content) {
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setAllowDuplicateKeys(false);
Yaml yaml = new Yaml(loaderOptions);
yaml.load(content);
}
}
public interface ConfigSyntaxErrorHandler {
// 语法检测失败之后的错误处理
void onError(NacosConfigProperties nacosConfigProperties, NacosPropertySource nacosPropertySource, String errorMsg);
}
// 内置的错误处理,这里只打日志,其他诉求可以自定义接口实现扩展
public class LogConfigSyntaxErrorHandler implements ConfigSyntaxErrorHandler {
@Override
public void onError(NacosConfigProperties nacosConfigProperties, NacosPropertySource nacosPropertySource, String errorMsg) {
log.error("nacos config content check error, server={}, namespace={}, dataId={}, group={}, msg={}",
nacosConfigProperties.getServerAddr(), nacosConfigProperties.getNamespace(),
nacosPropertySource.getDataId(), nacosPropertySource.getGroup(),
errorMsg);
}
}