核心是 ScheduledExecutorService 替代 Timer 避免单点崩溃,正确配置 JavaMail 的 SMTP 认证、TLS 和发件人合法性以确保邮件稳定抵达收件箱。

Java 中开发小型邮件提醒工具,核心不在“定时任务框架选哪个”,而在于 Timer 和 ScheduledExecutorService 的实际行为差异是否被你踩过坑——比如任务抛异常后整个调度静默停止,或者邮件发送阻塞导致后续提醒全部延迟。
用 ScheduledExecutorService 替代 Timer 避免单点崩溃
Timer 是单线程调度器,只要其中某个任务抛出未捕获异常(例如 AuthenticationFailedException 或网络超时),整个 Timer 就会终止,后续所有提醒彻底失效,且无日志提示。而 ScheduledExecutorService 每个任务在独立线程中执行,异常不会影响其他任务。
- 始终用
Executors.newSingleThreadScheduledExecutor(),而非newFixedThreadPool(1),前者能保证任务串行且支持优雅关闭 - 务必在任务
Runnable内部包裹try-catch,至少记录异常:executor.scheduleAtFixedRate(() -> { try { sendReminderEmail(); } catch (Exception e) { logger.error("邮件提醒执行失败", e); } }, 0, 5, TimeUnit.MINUTES); - 不要依赖
Timer.cancel()清理资源;改用executor.shutdown()+awaitTermination()
JavaMail API 发送前必须验证的三件事
90% 的“发不出邮件”问题不是代码逻辑错,而是配置或权限没到位。以下三项缺一不可,且顺序不能颠倒:
-
mail.smtp.auth必须设为true,否则 Gmail、Outlook 等现代 SMTP 服务直接拒绝连接 -
mail.smtp.starttls.enable必须为true(Gmail)或false(部分企业 SMTP),不能靠猜测;查目标邮箱文档确认 - 账号密码不能直接写明文——使用应用专用密码(Gmail)或开启 SMTP 授权(QQ 邮箱),普通登录密码会触发安全拦截
示例关键配置段(Gmail):
Properties props = new Properties();
props.put("mail.smtp.host", "smtp.gmail.com");
props.put("mail.smtp.port", "587");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true"); // 注意这里是 true
Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("your@gmail.com", "your-app-password");
}
});
避免邮件内容被当垃圾邮件的硬性细节
即使 SMTP 连通、认证通过,收件方服务器仍可能直接扔进垃圾箱。以下三点是 JavaMail 中最容易被忽略的“可信度信号”:
立即学习“Java免费学习笔记(深入)”;
- 必须设置
MimeMessage.setFrom(new InternetAddress("noreply@yourdomain.com")),不能只用setRecipients;伪造from域名会被多数反垃圾系统拒收 - 主题(
setSubject)和正文(setText或setContent)里避免连续感叹号、全大写单词、敏感词如“免费”“限时” - 如果用 HTML 正文,必须同时提供纯文本备选版本:
message.setContent(multipart); // multipart 含 text/plain + text/html part
真正难的不是写出发邮件的几行代码,而是让每封邮件稳定抵达收件箱——这取决于你是否把 SMTP 认证方式、TLS 启用时机、发件地址合法性这些“非 Java 逻辑”当作和业务代码同等重要的部分来对待。










