相信大家碰到源码时经常无从下手🙃,不知道从哪开始阅读,面对大量代码晕头转向,索性就读不下去了,又浪费了一次提升自己的机会😭。
我认为有一种方法,可以解决大家的困扰!那就是通过阅读某一次开源的【PR】,从这个入口出发去阅读源码!!
至此,我们发现自己开始从大量堆砌的源码中脱离开来😀,柳暗花明又一村。
一、前瞻
Ok,开始我们今天的PR阅读。
翻译过来大致意思就是添加提醒通知的功能。翻译如下:
支持提醒通知设计
- Shenyu admin 提供警报报告 API,
/alert/report
用于从网关 pulgin 接收警报内容- 网关在警报触发时发送警报消息
- 神宇仪表板支持管理警报接收者名称,警报类型(电子邮件,钉钉,微信...)
我们可以思考下今天的阅读线索了。
- 什么情况下会触发该警报信息
- 要支持多种警报类型,贡献者的代码是怎么设计成可扩展的
二、探索
话不多说,先整体看下本次PR的整体提交,从全局看下做了哪些修改。
还有很多提交没有截图下来,本次提交代码量可谓巨大了,那我们就先看看最核心的功能shenyu-alert模块。
先思考下,既然是告警,核心接口就是发送告警接口,可以看到AlertNotifyHandler的send接口,我们就从这个入口开始探索,看看线索1的答案。
public interface AlertNotifyHandler {
/**
* send alert.
*
* @param receiver Notification configuration information
* @param alert Alarm information
* @throws AlertNoticeException when send receiver error
*/
void send(AlertReceiverDTO receiver, AlarmContent alert) throws AlertNoticeException;
/**
* alert type.
*
* @return type
*/
byte type();
}
通过引用来查询,发现send()方法最终的调用者是通过Controller来触发告警,很奇怪,告警不是应该内部触发吗?
@RestApi("/alert/report")
public class AlertReportController {
@Autowired
private AlertDispatchService alertDispatchService;
/**
* report new alert content.
*
* @param alarmContent AlertContentDTO
* @return row int
*/
@PostMapping
public ShenyuAdminResult reportAlert(@Valid @RequestBody final AlarmContent alarmContent) {
alertDispatchService.dispatchAlert(alarmContent);
return ShenyuAdminResult.success(ShenyuResultMessage.CREATE_SUCCESS);
}
}
我们重新回顾下贡献者在PR写下的注释,有提到下面这一条:
Shenyu admin 提供警报报告 API,
/alert/report
用于从网关 pulgin 接收警报内容
也就是说告警是通过http请求触发,同时触发对象是网关的各个pulgin插件。
那我们再看看pulgin插件什么情况下会发送告警的http请求呢。
我们直接通过全局搜索/alert/report
,看看哪些地方触发该http请求。
再找到对应执行的代码:
public class GlobalErrorHandler implements ErrorWebExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(GlobalErrorHandler.class);
/**
* handler error.
*
* @param exchange the exchange
* @param throwable the throwable
* @return error result
*/
@Override
@NonNull
public Mono<Void> handle(@NonNull final ServerWebExchange exchange, @NonNull final Throwable throwable) {
LOG.error("handle error: {} formatError:{} throwable:", exchange.getLogPrefix(), formatError(throwable, exchange.getRequest()), throwable);
HttpStatus httpStatus;
Object errorResult;
String errorMsg = "";
if (throwable instanceof IllegalArgumentException) {
httpStatus = HttpStatus.BAD_REQUEST;
errorResult = ShenyuResultWrap.error(exchange, httpStatus.value(), throwable.getMessage(), null);
errorMsg = throwable.getMessage();
} else if (throwable instanceof ResponseStatusException) {
httpStatus = ((ResponseStatusException) throwable).getStatus();
String errMsg = StringUtils.hasLength(((ResponseStatusException) throwable).getReason()) ? ((ResponseStatusException) throwable).getReason() : httpStatus.getReasonPhrase();
errorResult = ShenyuResultWrap.error(exchange, httpStatus.value(), errMsg, null);
errorMsg = errMsg;
} else {
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
errorResult = ShenyuResultWrap.error(exchange, httpStatus.value(), httpStatus.getReasonPhrase(), null);
errorMsg = httpStatus.getReasonPhrase();
}
exchange.getResponse().setStatusCode(httpStatus);
Map<String, String> labels = new HashMap<>(8);
labels.put("global", "error");
labels.put("component", "gateway");
AlarmSender.alarmMediumCritical("ShenYu-Gateway-Global-Error", errorMsg, labels);
return WebFluxResultUtils.result(exchange, errorResult);
}
}
可以看到调用者实现了Spring的ErrorWebExceptionHandler类,也就是说这个警报的实际调用者是用Spring内置的web错误处理器。
到这里我们就解决了我们的阅读线索1了。
什么情况下会触发该警报信息
还没完呢,我们继续阅读线索2的探索:要支持多种警报类型,贡献者的代码是怎么设计成可扩展的。
既然要可扩展,肯定有底层接口在设定规则,我们找下这个底层接口。
这个底层接口其实还是我们上文提到的send接口,可以看到send方法的子类实现有钉钉、邮箱通知。
两个子类实现都是实现相同的底层接口AlertNotifyHandler,只要在配置上配置哪个通知实现,ShenYu alert模块便会实例化对应的通知实现,也就能达到可扩展的目的。
@Component
final class EmailAlertNotifyStrategy implements AlertNotifyHandler {
}
@Component
final class EmailAlertNotifyStrategy implements AlertNotifyHandler {
}
三、总结
在阅读中,还发现了有个html文件忘记加了开源协议,我们提下PR修复下,又收获了一次开源贡献!!
PR提交戳这。
未完待续。。。
好了,今天的分享就到这🤔。大家能否感受到通过PR这种方式来阅读源码的乐趣呢 !
创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️