定时任务中自定义异常需携带业务语义并统一捕获分级告警:继承runtimeexception表不可恢复故障(须告警),exception表可重试问题(仅日志);抛出时带traceid、任务名等上下文;通过joblistener或基类模板方法统一拦截;告警内容须含类名、时间、机器信息、业务标识及精简堆栈。

自定义异常在定时任务中触发告警,关键不是“抛出就告警”,而是让异常携带可识别的业务语义,并被统一捕获、分类、响应。直接 throw 一个自定义异常却不做配套处理,告警不会自动发生。
明确异常类型与告警意图
先决定这个异常该不该触发告警:
- 继承 RuntimeException(非受检异常):适合表示「程序逻辑错误、配置异常、不可恢复故障」,例如
TaskConfigInvalidException、ExternalServiceDownException—— 这类应进入告警通道 - 继承 Exception(受检异常):适合表示「预期内可重试的外部问题」,例如
NetworkRetryableException—— 通常只记录 warn 日志 + 自动重试,不告警 - 避免泛化命名,如
MyException;推荐带业务域前缀,如ReportGenerationFailedException
在定时任务里精准抛出并传递上下文
不要只抛空异常,要带关键信息:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 构造时传入 traceId、任务名、失败数据 ID 等,便于告警关联定位:
throw new ReportGenerationFailedException("生成月报失败", "TASK_MONTHLY_REPORT", MDC.get("traceId"), "202609") - 若使用 Spring @Scheduled,可在方法内 try-catch 后主动 throw 自定义异常,确保它穿透到调度框架外层
- 避免在 finally 或异步线程中抛出自定义异常 —— 上下文(如 MDC)可能已丢失,告警信息不全
统一拦截 + 分级告警
靠 try-catch 散布在每个任务里不现实,要用全局机制捕获:
- 对 Quartz:实现
JobListener的jobWasExecuted方法,检查JobExecutionContext.getJobResult()是否为异常实例,再判断是否为你的自定义异常子类 - 对 Spring Task:用
@ControllerAdvice不适用(它只管 Web 层),改用TaskExecutionAdvisor或自定义ScheduledTaskRegistrar注册增强逻辑 - 最稳妥方式:所有定时任务基类封装 execute() 模板方法,在 catch 块中做类型判断:
if (e instanceof ReportGenerationFailedException) { alertService.warn(e); }
告警内容要带可操作线索
光说“任务失败”没用,收件人需要立刻知道怎么查:
- 钉钉/邮件标题示例:
【PROD】月报生成失败|服务:report-svc|任务:TASK_MONTHLY_REPORT|traceId:a1b2c3 - 正文中必须包含:
• 异常完整类名与 message
• 发生时间(精确到毫秒)
• 所在机器 hostname 与 JVM PID
• 关联的业务标识(如报表周期、订单批次号) - 堆栈只截取前 10 行,避免邮件超长;完整日志靠 traceId 在 ELK 中检索
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










