Java邮件编程全指南:从入门到精通
这是一份非常详细、实用、通俗易懂、权威且全面的 Java 邮件编程指南。希望这篇文章能对你有帮助,关注我,你的肯定就是我输出的最大动力!
目录
-
邮件基础与协议
- 1.1 邮件是如何工作的?
- 1.2 SMTP (简单邮件传输协议)
- 1.3 POP3 (邮局协议第3版)
- 1.4 IMAP (互联网消息访问协议)
- 1.5 SMTP vs POP3 vs IMAP
- 1.6 MIME (多用途互联网邮件扩展)
-
JavaMail API 概览
- 2.1 JavaMail 是什么?
- 2.2 核心组件介绍
SessionMessageTransport(发送)Store(接收)Folder
- 2.3 依赖引入 (Maven / Gradle / JARs)
-
配置与发送邮件 (SMTP)
- 3.1 设置 Session 属性
- 3.1.1 基本属性 (
mail.smtp.host,mail.smtp.port) - 3.1.2 身份验证 (
mail.smtp.auth) - 3.1.3 传输层安全 (
mail.smtp.starttls.enable,mail.smtp.ssl.enable) - 3.1.4 调试 (
mail.debug)
- 3.1.1 基本属性 (
- 3.2 创建邮件消息 (
Message)- 3.2.1 设置发件人、收件人、抄送、密送 (
From,To,Cc,Bcc) - 3.2.2 设置主题 (
Subject) - 3.2.3 设置正文 (
setText/setContent) - 3.2.4 设置日期 (
setSentDate) - 3.2.5 设置邮件头 (自定义头信息)
- 3.2.1 设置发件人、收件人、抄送、密送 (
- 3.3 发送邮件 (
Transport) - 3.4 发送纯文本邮件 (完整示例代码)
- 3.5 发送 HTML 邮件
- 3.6 发送带附件的邮件 (
Multipart,MimeBodyPart) - 3.7 发送带内嵌图片的 HTML 邮件 (
Content-ID) - 3.8 常见 SMTP 服务器配置示例 (Gmail, QQ, 163, 企业邮箱)
- 3.1 设置 Session 属性
-
接收邮件 (POP3 / IMAP)
- 4.1 设置 Session 属性 (接收侧)
- 4.1.1 POP3 (
mail.pop3.host,mail.pop3.port,mail.pop3.starttls.enable,mail.pop3.ssl.enable) - 4.1.2 IMAP (
mail.imap.host,mail.imap.port,mail.imap.starttls.enable,mail.imap.ssl.enable)
- 4.1.1 POP3 (
- 4.2 连接到邮件存储 (
Store) - 4.3 打开邮箱文件夹 (
Folder)- 4.3.1 常见文件夹 (
INBOX,Sent,Drafts,Trash) - 4.3.2 文件夹访问模式 (
READ_ONLY,READ_WRITE)
- 4.3.1 常见文件夹 (
- 4.4 获取邮件列表
- 4.5 解析邮件内容 (
Message)- 4.5.1 获取发件人、收件人、主题、日期
- 4.5.2 获取纯文本正文 (
getContent/getInputStream) - 4.5.3 获取 HTML 正文
- 4.5.4 获取附件 (
Multipart,BodyPart,getContentType,getDisposition,getInputStream,saveFile)
- 4.6 使用 POP3 接收邮件 (完整示例代码)
- 4.7 使用 IMAP 接收邮件 (完整示例代码)
- 4.8 搜索邮件 (
Folder.search(SearchTerm term)) - 4.9 删除邮件 (
message.setFlag(Flags.Flag.DELETED, true),folder.expunge()) - 4.10 邮件回复
- 4.11 邮件抄送与密送
- 1.12 邮件转发
- 4.13 邮件附件下载
- 4.14 邮件标记已读与未读
- 4.15 邮件回执
- 4.1 设置 Session 属性 (接收侧)
-
处理复杂邮件结构与 MIME
- 5.1
Multipart深入理解 - 5.2
MimeBodyPart详解 - 5.3 内容类型 (
Content-Type) 详解 (text/plain,text/html,image/jpeg,application/pdf,multipart/mixed,multipart/alternative,multipart/related) - 5.4 内容传输编码 (
Content-Transfer-Encoding) (7bit,8bit,base64,quoted-printable) - 5.5 内容处置 (
Content-Disposition) (inline,attachment) - 5.6 处理嵌套的
Multipart结构
- 5.1
-
安全性与最佳实践
- 6.1 SSL/TLS 加密的重要性
- 6.2 安全地存储密码 (避免硬编码)
- 6.3 处理敏感信息
- 6.4 防范邮件注入攻击
- 6.5 连接池与资源管理 (
Transport和Store的关闭) - 6.6 错误处理与重试机制
- 6.7 日志记录
-
常见问题与故障排除
- 7.1 认证失败 (
AuthenticationFailedException) - 7.2 连接超时 (
ConnectException) - 7.3 SSL/TLS 证书问题 (
SSLHandshakeException) - 7.4 端口被阻止 (防火墙)
- 7.5 附件无法下载或显示
- 7.6 HTML 邮件样式丢失
- 7.7 中文乱码问题 (字符集设置)
- 7.1 认证失败 (
-
完整系统案例
- 案例一:邮件发送工具类 (支持文本、HTML、附件、图片)
- 案例二:邮件接收监控服务 (IMAP IDLE/Polling)
- 案例三:简单的邮件客户端 (命令行或 Swing)
- 案例四:Spring Boot 集成邮件发送
1. 邮件基础与协议
要理解 Java 邮件编程,首先要明白电子邮件系统是如何工作的。它依赖于一系列标准协议。
-
1.1 邮件是如何工作的?
- 当你发送邮件时,你的邮件客户端 (如 Outlook, Thunderbird) 或应用程序使用 SMTP 协议将邮件发送到你的邮件服务器 (SMTP Server)。
- 你的邮件服务器再将邮件通过 SMTP 协议转发给收件人的邮件服务器。
- 收件人使用邮件客户端或应用程序,通过 POP3 或 IMAP 协议连接到他们的邮件服务器来下载或查看邮件。
- MIME 定义了邮件内容的格式,如文本、HTML、图片、附件等如何编码和在邮件中表示。
-
1.2 SMTP (简单邮件传输协议)
- 作用: 负责将邮件从发送方传输到接收方的邮件服务器。只负责发送。
- 端口: 默认端口 25 (非加密)。安全端口 465 (SSL) 或 587 (STARTTLS)。
- 过程: 建立连接 -> 身份验证 (可选) -> 发送邮件信息 (
MAIL FROM,RCPT TO,DATA) -> 断开连接。 - Java 对应:
Transport类。
-
1.3 POP3 (邮局协议第3版)
- 作用: 允许邮件客户端从邮件服务器下载邮件。通常下载后会从服务器删除邮件 (可配置)。
- 端口: 默认端口 110 (非加密)。安全端口 995 (SSL)。
- 特点: 简单,离线访问为主。邮件通常存储在本地。不支持多文件夹同步。
- Java 对应:
Store类 (连接 POP3 服务器)。
-
1.4 IMAP (互联网消息访问协议)
- 作用: 允许邮件客户端访问邮件服务器上的邮件。邮件保留在服务器上。支持同步多个客户端的状态、文件夹管理、邮件搜索等。
- 端口: 默认端口 143 (非加密)。安全端口 993 (SSL)。
- 特点: 更强大,支持在线和离线操作。邮件状态 (已读/未读、星标等) 在服务器同步。
- Java 对应:
Store类 (连接 IMAP 服务器)。
-
1.5 SMTP vs POP3 vs IMAP
协议 方向 主要功能 邮件存储位置 特点 SMTP 发送 发送邮件 - 发送端使用 POP3 接收 下载邮件 本地 简单,离线为主 IMAP 接收 访问和同步邮件 服务器 功能强大,支持同步 -
1.6 MIME (多用途互联网邮件扩展)
- 作用: 扩展了原始只能发送纯文本的邮件协议 (RFC 822),允许在邮件中包含:
- 非文本内容 (图片、音频、视频、应用程序文件等)。
- 多语言字符集 (解决乱码)。
- 富文本格式 (HTML)。
- 组合内容 (正文+附件,正文+内嵌图片)。
- 核心概念:
Content-Type: 定义内容的类型和子类型 (如text/plain,text/html,image/jpeg,multipart/mixed)。Content-Transfer-Encoding: 定义内容如何编码以便安全传输 (如7bit,8bit,base64,quoted-printable)。Content-Disposition: 指示内容如何显示 (inline- 内嵌在邮件中显示,attachment- 作为附件下载)。Multipart: MIME 消息可以包含多个部分 (BodyPart),每个部分有自己的头信息和内容。multipart/mixed用于混合独立部分 (如正文+附件),multipart/alternative用于同一内容的不同版本 (如纯文本+HTML),multipart/related用于相关资源 (如 HTML + 内嵌图片)。
- Java 对应:
MimeMessage,MimeMultipart,MimeBodyPart类。
- 作用: 扩展了原始只能发送纯文本的邮件协议 (RFC 822),允许在邮件中包含:
2. JavaMail API 概览
-
2.1 JavaMail 是什么? JavaMail API 是 Java 平台的一个标准扩展 (JSR 919),提供了一套独立于协议的框架和抽象,用于构建邮件和消息应用程序。它封装了 SMTP、POP3、IMAP 等协议的底层细节,开发者可以使用统一的 API 来发送和接收邮件。
-
2.2 核心组件介绍
Session(会话):- 邮件操作的核心配置点。
- 包含连接邮件服务器所需的所有属性 (主机名、端口、用户名、密码、协议、加密设置等)。
- 通过
Properties对象或Authenticator对象创建。 Session session = Session.getInstance(properties, authenticator);
Message(消息):- 表示一封邮件。
- 通常是
MimeMessage(支持 MIME 格式)。 - 包含发件人、收件人、主题、正文、日期、邮件头等信息。
Message message = new MimeMessage(session);
Transport(传输):- 用于发送邮件。
- 与 SMTP 服务器通信。
- 通过
Session获得:Transport transport = session.getTransport("smtp"); - 使用方法:
transport.connect();->transport.sendMessage(message, message.getAllRecipients());->transport.close();
Store(存储):- 用于访问邮件存储 (邮箱)。
- 与 POP3 或 IMAP 服务器通信。
- 通过
Session获得:Store store = session.getStore("imap");或Store store = session.getStore("pop3"); - 使用方法:
store.connect(username, password);-> 打开Folder-> 操作邮件 -> 关闭Folder->store.close();
Folder(文件夹):- 代表邮箱中的一个文件夹 (如 INBOX, Sent Mail)。
- 通过
Store获得:Folder folder = store.getFolder("INBOX"); - 使用方法:
folder.open(Folder.READ_ONLY);或folder.open(Folder.READ_WRITE);-> 获取邮件 (Message[] messages = folder.getMessages();) -> 操作邮件 ->folder.close(false);(false 表示不删除标记为DELETED的邮件) 或folder.close(true);(true 表示删除标记为DELETED的邮件)。
-
2.3 依赖引入 JavaMail API 不是标准 Java SE 的一部分,需要额外引入。
- 官方 JARs: 从 JavaMail Project 下载
javax.mail.jar。 - Maven:
com.sun.mail javax.mail1.6.2 - Gradle:
implementation 'com.sun.mail:javax.mail:1.6.2' // 请检查最新版本 - 另外,处理邮件内容 (如解析 HTML) 可能还需要引入其他库,如
javax.activation:activation(用于 MIME 类型处理和数据源),但在较新版本中可能已包含或使用其他实现。
- 官方 JARs: 从 JavaMail Project 下载
3. 配置与发送邮件 (SMTP)
-
3.1 设置 Session 属性 创建一个
Properties对象来配置Session。Properties props = new Properties();- 3.1.1 基本属性:
props.put("mail.smtp.host", "smtp.example.com"); // SMTP服务器地址 props.put("mail.smtp.port", "587"); // SMTP服务器端口 (25, 465, 587) - 3.1.2 身份验证: (大多数服务器需要)
props.put("mail.smtp.auth", "true"); // 启用认证 - 3.1.3 传输层安全:
- SSL: 直接使用 SSL 加密连接 (端口通常是 465)
props.put("mail.smtp.ssl.enable", "true"); // 启用 SSL // 如果使用 SSL, 端口通常设置为 465 props.put("mail.smtp.port", "465"); - STARTTLS: 先建立非加密连接,然后升级到加密 (端口通常是 25 或 587)
props.put("mail.smtp.starttls.enable", "true"); // 启用 STARTTLS // 端口通常为 587 props.put("mail.smtp.port", "587");
- SSL: 直接使用 SSL 加密连接 (端口通常是 465)
- 3.1.4 调试: (开发时很有用)
props.put("mail.debug", "true"); // 启用调试输出
创建带认证的 Session:
Session session = Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("your_username@example.com", "your_password"); } });或者使用更简洁的方式 (Java 8+):
Session session = Session.getInstance(props, new javax.mail.Authenticator() { protected javax.mail.PasswordAuthentication getPasswordAuthentication() { return new javax.mail.PasswordAuthentication(username, password); } }); - 3.1.1 基本属性:
-
3.2 创建邮件消息 (
Message) 使用MimeMessage类。Message message = new MimeMessage(session);- 3.2.1 设置发件人、收件人、抄送、密送:
// 设置发件人 (可以是字符串或 Address 对象) message.setFrom(new InternetAddress("sender@example.com")); // 设置主收件人 (To) message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("recipient1@example.com, recipient2@example.com")); // 设置抄送 (Cc) message.setRecipients(Message.RecipientType.CC, InternetAddress.parse("cc@example.com")); // 设置密送 (Bcc) message.setRecipients(Message.RecipientType.BCC, InternetAddress.parse("bcc@example.com"));InternetAddress.parse可以解析逗号分隔的地址列表。 - 3.2.2 设置主题:
message.setSubject("这是一封测试邮件主题"); - 3.2.3 设置正文:
- 纯文本:
- HTML 内容 (稍后详细讲解):
message.setContent("HTML 内容
这是一封包含 HTML 格式的邮件。
", "text/html; charset=UTF-8");
- 3.2.4 设置日期: (可选,默认是当前时间)
message.setSentDate(new Date()); - 3.2.5 设置邮件头: (自定义头信息,非必须)
message.setHeader("X-Custom-Header", "MyValue");
- 3.2.1 设置发件人、收件人、抄送、密送:
-
3.3 发送邮件 (
Transport)try (Transport transport = session.getTransport()) { // 连接服务器 (如果 Session 属性中设置了用户名密码,这里通常不需要再提供) transport.connect(); // 或者 transport.connect(username, password); 如果 Session 未设置认证 // 发送邮件 transport.sendMessage(message, message.getAllRecipients()); System.out.println("邮件发送成功!"); } catch (MessagingException e) { e.printStackTrace(); // 处理发送异常 } // 使用 try-with-resources 确保 Transport 自动关闭 -
3.4 发送纯文本邮件 (完整示例代码)
import java.util.Properties; import javax.mail.*; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; public class SimpleTextMailSender { public static void main(String[] args) { // 邮件服务器配置 String host = "smtp.example.com"; // 替换为你的SMTP服务器 int port = 587; // 常用端口 25(非SSL), 465(SSL), 587(STARTTLS) String username = "your_username@example.com"; // 替换为你的邮箱用户名 String password = "your_password"; // 替换为你的邮箱密码/授权码 String from = "sender@example.com"; // 发件人地址 String to = "recipient@example.com"; // 收件人地址 // 设置属性 Properties props = new Properties(); props.put("mail.smtp.host", host); props.put("mail.smtp.port", port); props.put("mail.smtp.auth", "true"); // 启用认证 props.put("mail.smtp.starttls.enable", "true"); // 启用 STARTTLS (对于端口587) // 创建会话 Session session = Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); try { // 创建邮件 Message message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); message.setSubject("JavaMail 纯文本测试邮件"); message.setText("你好, 这是一封使用 JavaMail API 发送的纯文本测试邮件。 祝好!"); // 发送邮件 Transport.send(message); // 简化发送方式 (内部处理 Transport) System.out.println("纯文本邮件发送成功!"); } catch (MessagingException e) { throw new RuntimeException("邮件发送失败", e); } } } -
3.5 发送 HTML 邮件 修改正文设置部分:
String htmlContent = "" + "HTML 邮件测试
" + "这是一封HTML格式的邮件。
" + "可以包含 样式 和 链接。
" + ""; message.setContent(htmlContent, "text/html; charset=UTF-8");注意:某些邮件客户端可能只显示纯文本版本或限制部分 HTML/CSS。确保提供良好的降级体验或同时提供纯文本版本 (见
Multipart/alternative)。 -
3.6 发送带附件的邮件 需要使用
MimeMultipart和MimeBodyPart来组合多个部分。import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeBodyPart; import javax.activation.DataHandler; import javax.activation.FileDataSource; import java.io.File; // ... (Session 和 Message 创建同上) // 创建一个多部分消息 MimeMultipart multipart = new MimeMultipart(); // 创建文本正文部分 MimeBodyPart textPart = new MimeBodyPart(); textPart.setText("邮件正文内容,附件在下面。"); // 或使用 setContent 设置 HTML multipart.addBodyPart(textPart); // 创建附件部分 MimeBodyPart attachmentPart = new MimeBodyPart(); File file = new File("/path/to/your/file.pdf"); // 附件文件路径 FileDataSource source = new FileDataSource(file); attachmentPart.setDataHandler(new DataHandler(source)); attachmentPart.setFileName(file.getName()); // 设置附件显示的文件名 multipart.addBodyPart(attachmentPart); // 将多部分内容设置为邮件内容 message.setContent(multipart); // ... (发送邮件同上) -
3.7 发送带内嵌图片的 HTML 邮件 原理:将图片作为
BodyPart嵌入,并设置其Content-ID,然后在 HTML 中使用cid:ContentID引用。// ... (Session 和 Message 创建同上) // 创建一个多部分消息 (使用 'related' 类型表示内嵌资源相关) MimeMultipart multipart = new MimeMultipart("related"); // 创建 HTML 正文部分 (父部分) MimeBodyPart htmlPart = new MimeBodyPart(); // HTML 内容,引用图片的 cid String htmlContent = "" + "包含图片的邮件
" + "图片内嵌在邮件中:
" + "" // 注意 https://blog.csdn.net/suodasheng/article/details/cid:image1 + ""; htmlPart.setContent(htmlContent, "text/html; charset=UTF-8"); multipart.addBodyPart(htmlPart); // 创建图片部分 (附件,但设置为内嵌) MimeBodyPart imagePart = new MimeBodyPart(); File imageFile = new File("/path/to/image.jpg"); FileDataSource imageSource = new FileDataSource(imageFile); imagePart.setDataHandler(new DataHandler(imageSource)); imagePart.setHeader("Content-ID", "
"); // 设置 Content-ID, 与 HTML 中的 cid 对应 imagePart.setDisposition(MimeBodyPart.INLINE); // 设置为内嵌 multipart.addBodyPart(imagePart); message.setContent(multipart); // ... (发送邮件同上) -
3.8 常见 SMTP 服务器配置示例
- Gmail (使用 App Password):
- 在 Gmail 设置中启用 IMAP/POP (通常默认启用)。
- 开启“两步验证”。
- 生成一个“应用专用密码”。
- 配置:
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"); // username 是你的完整 Gmail 地址 // password 是你生成的应用专用密码 (不是你的 Gmail 密码)
- QQ 邮箱:
- 登录 QQ 邮箱网页版,在“设置”->“账户”中开启 POP3/SMTP 服务。可能需要短信验证。
- 开启后会获得一个授权码 (不是邮箱密码)。
- 配置:
props.put("mail.smtp.host", "smtp.qq.com"); props.put("mail.smtp.port", "465"); // 或 587 (SSL 或 STARTTLS) props.put("mail.smtp.auth", "true"); props.put("mail.smtp.ssl.enable", "true"); // 对于端口 465 // 或者 // props.put("mail.smtp.port", "587"); // props.put("mail.smtp.starttls.enable", "true"); // 对于端口 587 // username 是你的完整 QQ 邮箱地址 // password 是你获得的授权码
- 163/126 邮箱: 类似 QQ 邮箱,开启 SMTP 服务,使用授权码。
props.put("mail.smtp.host", "smtp.163.com"); // 或 smtp.126.com props.put("mail.smtp.port", "465"); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.ssl.enable", "true"); // username 是你的完整邮箱地址 // password 是你设置的授权码 - 企业邮箱: 咨询你的企业邮箱管理员获取 SMTP 服务器地址、端口、加密方式、用户名 (可能是完整邮箱或用户名) 和密码 (可能是邮箱密码或特定密码)。
- Gmail (使用 App Password):
4. 接收邮件 (POP3 / IMAP)
-
4.1 设置 Session 属性 (接收侧) 创建
Properties对象。Properties props = new Properties();- 4.1.1 POP3:
props.put("mail.pop3.host", "pop.example.com"); // POP3服务器地址 props.put("mail.pop3.port", "995"); // POP3端口 (110非加密, 995 SSL) props.put("mail.pop3.ssl.enable", "true"); // 启用SSL (端口995) // 或者使用 STARTTLS (较少见) // props.put("mail.pop3.port", "110"); // props.put("mail.pop3.starttls.enable", "true"); - 4.1.2 IMAP:
props.put("mail.imap.host", "imap.example.com"); // IMAP服务器地址 props.put("mail.imap.port", "993"); // IMAP端口 (143非加密, 993 SSL) props.put("mail.imap.ssl.enable", "true"); // 启用SSL (端口993) // 或者使用 STARTTLS // props.put("mail.imap.port", "143"); // props.put("mail.imap.starttls.enable", "true"); props.put("mail.imap.partialfetch", "false"); // 可选,控制是否只获取邮件部分内容
创建带认证的 Session (与发送类似):
Session session = Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("your_username@example.com", "your_password"); } }); - 4.1.1 POP3:
-
4.2 连接到邮件存储 (
Store)try (Store store = session.getStore("pop3")) { // 或 "imap" store.connect(username, password); System.out.println("成功连接到邮件存储"); // ... 后续操作文件夹 } catch (MessagingException e) { e.printStackTrace(); } -
4.3 打开邮箱文件夹 (
Folder)try (Folder folder = store.getFolder("INBOX")) { // 指定文件夹名 // 打开文件夹 (只读模式) folder.open(Folder.READ_ONLY); // 或 Folder.READ_WRITE 允许修改/删除邮件 System.out.println("邮件总数: " + folder.getMessageCount()); System.out.println("新邮件数: " + folder.getNewMessageCount()); // 仅 IMAP 支持新邮件计数 // ... 后续获取邮件 } // try-with-resources 会自动关闭 Folder- 4.3.1 常见文件夹:
"INBOX"(收件箱),"Sent"(已发送),"Drafts"(草稿),"Trash"(垃圾箱/已删除),"Spam"(垃圾邮件)。名称可能因服务器和语言设置而异。IMAP 支持列出所有文件夹 (store.getDefaultFolder().list())。 - 4.3.2 文件夹访问模式:
Folder.READ_ONLY: 只读,不能修改邮件。Folder.READ_WRITE: 可读写,可以修改邮件状态、删除邮件等。
- 4.3.3 IMAP支持文件夹管理。原理:使用Folder类创建、重命名或移动文件夹。代码示例:
Store store = session.getStore(); store.connect("your-email@example.com", "your-password"); // 获取根文件夹 Folder root = store.getDefaultFolder(); Folder[] folders = root.list(); // 列出所有文件夹 // 创建新文件夹 Folder newFolder = root.getFolder("Work"); if (!newFolder.exists()) { newFolder.create(Folder.HOLDS_MESSAGES); // 创建文件夹 } // 重命名文件夹 Folder oldFolder = root.getFolder("Old"); oldFolder.renameTo(newFolder); // 移动邮件到文件夹 Message message = folder.getMessage(1); // 获取邮件 folder.copyMessages(new Message[]{message}, newFolder); // 复制到新文件夹 store.close();
- 4.3.1 常见文件夹:
-
4.4 获取邮件列表
// 获取所有邮件 (谨慎使用,邮件多时可能慢) Message[] messages = folder.getMessages(); // 获取指定范围的邮件 (例如最新10封) int start = Math.max(1, folder.getMessageCount() - 9); // 邮件编号从1开始 int end = folder.getMessageCount(); Message[] recentMessages = folder.getMessages(start, end); -
4.5 解析邮件内容 (
Message)for (Message message : messages) { // 4.5.1 获取基本信息 Address[] fromAddresses = message.getFrom(); String from = (fromAddresses != null && fromAddresses.length > 0) ? fromAddresses[0].toString() : "未知"; Address[] toAddresses = message.getRecipients(Message.RecipientType.TO); String toList = (toAddresses != null) ? InternetAddress.toString(toAddresses) : ""; String subject = message.getSubject(); Date sentDate = message.getSentDate(); System.out.println("邮件 #" + message.getMessageNumber()); System.out.println("发件人: " + from); System.out.println("收件人: " + toList); System.out.println("主题: " + subject); System.out.println("发送日期: " + sentDate); // 4.5.2 获取纯文本正文 Object content = message.getContent(); if (content instanceof String) { // 简单文本邮件 String textContent = (String) content; System.out.println("正文: " + textContent); } else if (content instanceof MimeMultipart) { // 复杂邮件 (多部分) MimeMultipart multipart = (MimeMultipart) content; handleMultipart(multipart); // 调用处理多部分的方法 (见下文) } System.out.println("------------------------------------"); }处理多部分邮件的方法 (
handleMultipart):private static void handleMultipart(MimeMultipart multipart) throws Exception { int partCount = multipart.getCount(); for (int i = 0; i < partCount; i++) { BodyPart bodyPart = multipart.getBodyPart(i); String disposition = bodyPart.getDisposition(); String contentType = bodyPart.getContentType(); // 4.5.3 获取 HTML 正文 / 4.5.4 获取附件 if (disposition != null && (disposition.equalsIgnoreCase(Part.ATTACHMENT) || disposition.equalsIgnoreCase(Part.INLINE))) { // 这是一个附件或内嵌资源 String fileName = bodyPart.getFileName(); if (fileName != null) { fileName = MimeUtility.decodeText(fileName); // 解码可能的编码文件名 } System.out.println("附件/资源: " + fileName + " (类型: " + contentType + ")"); // 保存附件到文件 saveAttachment(bodyPart, "/path/to/save/dir", fileName); } else { // 可能是文本正文 (纯文本或 HTML) Object partContent = bodyPart.getContent(); if (partContent instanceof String) { if (contentType.toLowerCase().contains("text/html")) { System.out.println("HTML 正文: " + partContent); } else if (contentType.toLowerCase().contains("text/plain")) { System.out.println("纯文本正文: " + partContent); } else { System.out.println("其他文本内容 (" + contentType + "): " + partContent); } } else if (partContent instanceof MimeMultipart) { // 嵌套的多部分 (递归处理) handleMultipart((MimeMultipart) partContent); } } } }保存附件的方法 (
saveAttachment):private static void saveAttachment(BodyPart bodyPart, String saveDir, String fileName) throws Exception { if (fileName == null || fileName.isEmpty()) { fileName = "attachment_" + System.currentTimeMillis() + ".dat"; } File saveFile = new File(saveDir, fileName); try (InputStream is = bodyPart.getInputStream(); FileOutputStream fos = new FileOutputStream(saveFile)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); } System.out.println("附件已保存到: " + saveFile.getAbsolutePath()); } } -
4.6 使用 POP3 接收邮件 (完整示例代码)
import java.util.Properties; import javax.mail.*; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeUtility; public class POP3MailReceiver { public static void main(String[] args) { // 邮件服务器配置 String host = "pop.example.com"; // POP3服务器 int port = 995; // POP3 SSL端口 String username = "your_username@example.com"; String password = "your_password"; String saveDir = "/path/to/save/attachments"; Properties props = new Properties(); props.put("mail.pop3.host", host); props.put("mail.pop3.port", port); props.put("mail.pop3.ssl.enable", "true"); Session session = Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); try (Store store = session.getStore("pop3")) { store.connect(); try (Folder folder = store.getFolder("INBOX")) { folder.open(Folder.READ_ONLY); Message[] messages = folder.getMessages(); System.out.println("收件箱中共有 " + messages.length + " 封邮件"); for (Message message : messages) { // 解析并打印邮件基本信息 (如前面4.5.1部分) // ... // 处理邮件内容 (调用 handleMultipart 或直接处理) Object content = message.getContent(); if (content instanceof String) { System.out.println("正文: " + content); } else if (content instanceof MimeMultipart) { handleMultipart((MimeMultipart) content, saveDir); } } } } catch (Exception e) { e.printStackTrace(); } } private static void handleMultipart(MimeMultipart multipart, String saveDir) throws Exception { // ... (实现如前面 handleMultipart 方法) } private static void saveAttachment(BodyPart bodyPart, String saveDir, String fileName) throws Exception { // ... (实现如前面 saveAttachment 方法) } } -
4.7 使用 IMAP 接收邮件 (完整示例代码) 结构与 POP3 示例非常相似,主要区别在于
Store协议和Folder操作 (IMAP 支持更多功能如搜索、标记)。import java.util.Properties; import javax.mail.*; public class IMAPMailReceiver { public static void main(String[] args) { // 邮件服务器配置 String host = "imap.example.com"; // IMAP服务器 int port = 993; // IMAP SSL端口 String username = "your_username@example.com"; String password = "your_password"; String saveDir = "/path/to/save/attachments"; Properties props = new Properties(); props.put("mail.imap.host", host); props.put("mail.imap.port", port); props.put("mail.imap.ssl.enable", "true"); props.put("mail.imap.partialfetch", "false"); // 获取完整邮件 Session session = Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); try (Store store = session.getStore("imap")) { store.connect(); try (Folder folder = store.getFolder("INBOX")) { folder.open(Folder.READ_ONLY); // 获取所有邮件 Message[] messages = folder.getMessages(); // 或者使用搜索 // SearchTerm term = new SubjectTerm("重要"); // Message[] messages = folder.search(term); for (Message message : messages) { // 解析邮件 (同 POP3 示例) // ... } } } catch (Exception e) { e.printStackTrace(); } } // ... handleMultipart 和 saveAttachment 方法同上 } -
4.8 搜索邮件 (IMAP 优势) IMAP 支持强大的搜索功能。使用
SearchTerm及其子类构建搜索条件。// 搜索主题包含 "报告" 的邮件 SearchTerm subjectTerm = new SubjectTerm("报告"); // 搜索来自特定发件人的邮件 SearchTerm fromTerm = new FromTerm(new InternetAddress("boss@company.com")); // 搜索未读邮件 SearchTerm unseenTerm = new FlagTerm(new Flags(Flags.Flag.SEEN), false); // 组合搜索条件 (AND) SearchTerm andTerm = new AndTerm(subjectTerm, fromTerm); // 组合搜索条件 (OR) SearchTerm orTerm = new OrTerm(subjectTerm, fromTerm); Message[] foundMessages = folder.search(searchTerm); -
4.9 删除邮件 (需要 READ_WRITE 模式)
folder.open(Folder.READ_WRITE); // 必须用读写模式打开才能删除 message.setFlag(Flags.Flag.DELETED, true); // 将邮件标记为删除 // 立即删除标记为删除的邮件 (谨慎使用) folder.expunge(); // 或者关闭文件夹时删除 folder.close(true); // true 表示删除标记为 DELETED 的邮件 -
4.10 邮件回复
回复邮件时引用原内容。原理:创建新邮件,设置In-Reply-To头。
Message originalMessage = folder.getMessage(1); // 获取原邮件
Message reply = new MimeMessage(session);
reply.setFrom(new InternetAddress("your-email@example.com"));
reply.setRecipients(Message.RecipientType.TO, originalMessage.getFrom());
reply.setSubject("Re: " + originalMessage.getSubject());
// 引用原内容
String content = "回复内容
原邮件:
" + originalMessage.getContent();
reply.setText(content);
// 设置回复头
reply.setHeader("In-Reply-To", originalMessage.getMessageID());
Transport.send(reply);
-
4.10 邮件抄送与密送
抄送(CC)和密送(BCC)在发送时设置收件人类型。
Message message = new MimeMessage(session);
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("to@example.com"));
message.setRecipients(Message.RecipientType.CC, InternetAddress.parse("cc@example.com")); // 抄送
message.setRecipients(Message.RecipientType.BCC, InternetAddress.parse("bcc@example.com")); // 密送
// 其余发送代码同第三章
-
4.12 邮件转发
转发邮件将原邮件作为附件发送。原理:创建新邮件,原邮件作为附件。
Message originalMessage = folder.getMessage(1);
Message forward = new MimeMessage(session);
forward.setFrom(new InternetAddress("your-email@example.com"));
forward.setRecipients(Message.RecipientType.TO, InternetAddress.parse("forward@example.com"));
forward.setSubject("转发: " + originalMessage.getSubject());
// 原邮件作为附件
MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.setContent(originalMessage, "message/rfc822"); // 设置邮件类型
attachmentPart.setFileName("original.eml");
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(attachmentPart);
forward.setContent(multipart);
Transport.send(forward);
-
4.13 邮件附件下载
接收邮件时下载附件。原理:检查邮件内容类型,如果为Multipart,遍历BodyPart获取附件。
import javax.mail.*;
import javax.mail.internet.*;
import java.io.*;
public class AttachmentDownloader {
public static void main(String[] args) {
// 使用IMAP接收邮件代码获取Message对象
try {
if (message.getContent() instanceof Multipart) {
Multipart multipart = (Multipart) message.getContent();
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition())) {
String fileName = bodyPart.getFileName();
InputStream is = bodyPart.getInputStream();
// 保存附件到本地
FileOutputStream fos = new FileOutputStream("downloads/" + fileName);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
fos.close();
is.close();
System.out.println("附件下载: " + fileName);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
-
4.14 邮件标记已读与未读
使用IMAP标记邮件状态。原理:设置邮件的SEEN标志。
// 在接收邮件代码中,使用IMAP
Message message = folder.getMessage(i); // 获取邮件
if (!message.isSet(Flags.Flag.SEEN)) {
message.setFlag(Flags.Flag.SEEN, true); // 标记为已读
}
- 4.15 邮件回执
邮件回执(Delivery Status Notification)通知发件人邮件是否送达。原理:发送邮件时设置请求回执头,服务器返回状态报告。
// 在发送邮件代码中,添加回执请求
Message message = new MimeMessage(session);
message.setHeader("Disposition-Notification-To", "your-email@example.com"); // 设置回执接收邮箱
// 其余发送代码同第三章
5. 处理复杂邮件结构与 MIME
-
5.1
Multipart深入理解Multipart是Part(如MimeMessage或MimeBodyPart) 内容的一种类型,表示该部分包含多个子部分 (BodyPart)。- 常见的
Multipart子类型:multipart/mixed: 包含独立的子部分。例如,一封包含正文文本和几个附件的邮件。multipart/alternative: 包含表示同一信息的不同版本。例如,一封同时包含纯文本版本和 HTML 版本的邮件。邮件客户端会选择最适合显示给用户的版本。multipart/related: 包含一个根部分 (通常是 HTML) 和与之相关的内嵌资源 (如图片)。根部分使用cid引用内嵌资源。multipart/signed: 用于数字签名。multipart/encrypted: 用于加密邮件。
- 这些子类型可以嵌套。例如,一个
multipart/mixed可能包含一个multipart/alternative(用于正文) 和一个attachment(用于附件)。
-
5.2
MimeBodyPart详解- 代表
Multipart中的一个子部分。 - 包含自己的头信息 (
Content-Type,Content-Transfer-Encoding,Content-Disposition,Content-ID等) 和内容。 - 内容可以是:
- 简单文本 (
String) - 二进制数据 (
InputStream/DataSource) - 另一个
Multipart(嵌套结构) - 一个
Message(表示作为附件包含的完整邮件 -message/rfc822)
- 简单文本 (
- 代表
-
5.3 内容类型 (
Content-Type) 详解- 格式:
type/subtype; parameters(如text/html; charset=UTF-8)。 - 常见类型:
text/plain: 纯文本。text/html: HTML 文本。image/jpeg,image/png,image/gif: 图片。application/pdf,application/msword,application/vnd.ms-excel: 应用程序文件。application/octet-stream: 通用二进制数据。multipart/*: 多部分类型 (如上所述)。message/rfc822: 嵌入的完整邮件。
charset参数对于文本类型非常重要 (如charset=UTF-8,charset=GBK),指定字符编码,解决乱码问题。
- 格式:
-
5.4 内容传输编码 (
Content-Transfer-Encoding)- 用于将二进制或非 ASCII 邮件内容编码为适合在 7-bit SMTP 通道中传输的格式。
- 常见编码:
7bit: 内容已经是 7-bit ASCII,短行 (<1000字符)。8bit: 内容包含 8-bit 字符,但行不太长。base64: 将二进制数据编码为 ASCII 字符 (字母、数字、+、/)。效率约为原始数据的 133%。适用于图像、可执行文件等。quoted-printable: 将非 ASCII 字符和特殊字符编码为=后跟两个十六进制数字。适用于主要包含 ASCII 字符但包含少量非 ASCII 字符的文本 (如带有重音符号的欧洲语言)。行长度有限制。
-
5.5 内容处置 (
Content-Disposition)- 指示邮件客户端如何处理该部分。
- 常见值:
inline: 该部分应在邮件正文中内嵌显示 (如 HTML 中的图片)。attachment: 该部分应作为附件下载,不直接显示在邮件正文中。
- 通常包含
filename参数指定附件保存时的默认文件名 (如Content-Disposition: attachment; filename="report.pdf")。文件名可能需要使用MimeUtility.decodeText解码。
-
5.6 处理嵌套的
Multipart结构 如前面handleMultipart方法所示,需要使用递归来遍历可能的多层嵌套结构。检查每个BodyPart的Content-Type是否是multipart/*,如果是,则递归调用处理多部分的方法。
6. 安全性与最佳实践
-
6.1 SSL/TLS 加密的重要性
- 始终使用 SSL/TLS (
mail.smtp.ssl.enable/mail.smtp.starttls.enable/mail.pop3.ssl.enable/mail.imap.ssl.enable) 来加密邮件传输通道,防止用户名、密码和邮件内容被窃听。 - 优先使用端口 465 (SMTP SSL), 993 (IMAP SSL), 995 (POP3 SSL) 或启用 STARTTLS 的端口 587 (SMTP), 143 (IMAP)。
- 始终使用 SSL/TLS (
-
6.2 安全地存储密码
- 切勿将密码硬编码在源代码中。
- 使用安全的方式存储密码:
- 环境变量。
- 配置文件 (确保文件权限安全)。
- 密钥管理系统 (如 HashiCorp Vault, AWS Secrets Manager)。
- 在运行时提示用户输入 (对于命令行工具)。
- 对于 Gmail 等,使用“应用专用密码”而非主密码。
-
6.3 处理敏感信息
- 避免在日志中记录完整的邮件内容、附件或敏感邮件头。
- 对包含敏感数据的邮件正文和附件进行适当处理 (如脱敏、加密存储)。
-
6.4 防范邮件注入攻击
- 当使用用户输入构建邮件 (如发件人地址、主题、正文) 时,务必进行严格的输入验证和清理。
- 避免直接将未经验证的用户输入拼接到 SMTP 命令或邮件头中。恶意输入可能注入额外的 SMTP 命令或修改邮件结构。
- 使用 JavaMail API 的方法 (如
setFrom,setRecipients,setSubject,setText) 来设置字段,它们内部会进行一定的处理。但仍需对输入进行验证。
-
6.5 连接池与资源管理
Transport和Store对象代表网络连接,是有限资源。- 总是在使用完毕后关闭它们 (
close()方法)。 - 使用
try-with-resources语句 (Java 7+) 确保资源自动关闭,即使在发生异常时。 - 如果需要频繁发送邮件,考虑使用连接池库 (如 Apache Commons Pool) 来管理
Transport对象,而不是为每封邮件创建新连接。但需注意服务器可能限制并发连接数。
-
6.6 错误处理与重试机制
- 使用
try-catch捕获MessagingException和其他可能的异常 (IOException,RuntimeException等)。 - 根据异常类型 (
AuthenticationFailedException,SendFailedException,ConnectException等) 进行不同的处理 (记录错误、通知用户、重试)。 - 对于暂时性错误 (如网络波动、服务器繁忙),实现重试逻辑 (带退避策略,如指数退避)。
- 使用
-
6.7 日志记录
- 使用日志框架 (如 SLF4J + Logback) 记录关键操作 (连接成功/失败、发送成功/失败、接收邮件数量、错误信息)。
- 合理设置日志级别 (
DEBUG用于开发调试,INFO用于常规操作,WARN/ERROR用于问题和异常)。 - 开启
mail.debug属性时,JavaMail 会将详细的协议交互输出到控制台,对调试非常有帮助,但在生产环境中应关闭。
7. 常见问题与故障排除
-
7.1 认证失败 (
AuthenticationFailedException)- 原因: 用户名或密码错误;服务器未启用 SMTP/POP3/IMAP 服务;需要应用专用密码 (如 Gmail) 或授权码 (如 QQ/163) 而使用了邮箱密码;账户被锁定。
- 解决: 仔细检查用户名密码;确认邮箱设置中已开启相应服务;对于 Gmail/QQ/163 等,使用正确的授权方式;尝试在网页邮箱登录验证账户状态。
-
7.2 连接超时 (
ConnectException)- 原因: 服务器地址错误;端口错误;服务器宕机;网络不通;防火墙阻止了连接。
- 解决: 验证服务器地址和端口;使用
telnet或nc命令测试是否能连接到服务器的端口;检查网络连接;检查防火墙设置。
-
7.3 SSL/TLS 证书问题 (
SSLHandshakeException)- 原因: 服务器证书不受信任 (自签名证书、证书过期、域名不匹配);Java 运行环境缺少根证书。
- 解决 (开发/测试): 对于自签名证书,可以临时信任所有证书 (不推荐生产环境):
props.put("mail.smtp.ssl.trust", "*"); // 信任所有主机 // 或者更精确地信任特定主机 // props.put("mail.smtp.ssl.trust", "smtp.example.com"); - 解决 (生产): 将服务器的有效证书导入 Java 的信任库 (
cacerts) 或配置应用使用包含该证书的信任库。
-
7.4 端口被阻止 (防火墙)
- 原因: 本地防火墙或公司网络防火墙阻止了到邮件服务器端口的出站连接。
- 解决: 联系网络管理员,请求开放相应的 SMTP (25, 465, 587) / POP3 (110, 995) / IMAP (143, 993) 端口。
-
7.5 附件无法下载或显示
- 原因:
Content-Disposition设置不正确;Content-Type识别错误;文件名编码问题;保存路径权限问题;邮件本身损坏。 - 解决: 检查代码中附件部分的处理和保存逻辑;使用
MimeUtility.decodeText解码文件名;检查保存目录是否存在且有写入权限;尝试用其他邮件客户端下载同一附件看是否正常。
- 原因:
-
7.6 HTML 邮件样式丢失
- 原因: 邮件客户端对 HTML/CSS 的支持有限 (特别是移动客户端);样式使用了外部 CSS (通常被阻止);样式使用了客户端不支持的 CSS 属性;内嵌图片未正确加载 (
cid引用错误)。 - 解决: 使用内联 CSS (
style="..."属性);避免使用复杂的 CSS;提供纯文本备选版本 (multipart/alternative);确保内嵌图片的Content-ID设置正确且在 HTML 中引用正确。
- 原因: 邮件客户端对 HTML/CSS 的支持有限 (特别是移动客户端);样式使用了外部 CSS (通常被阻止);样式使用了客户端不支持的 CSS 属性;内嵌图片未正确加载 (
-
7.7 中文乱码问题
- 原因: 邮件头 (主题、发件人/收件人名称) 或正文未正确指定字符集;使用的字符集与邮件客户端解码字符集不一致;附件文件名编码问题。
- 解决: 在设置主题、正文、附件文件名时,显式指定正确的字符集 (通常是 UTF-8):
接收时,使用message.setSubject("中文主题", "UTF-8"); // 设置主题和字符集 message.setText("中文正文", "UTF-8"); // 纯文本 // HTML message.setContent("中文
", "text/html; charset=UTF-8"); // 附件文件名 attachmentPart.setFileName(MimeUtility.encodeText("中文文件名.pdf", "UTF-8", "B")); // 使用 Base64 或 Q 编码MimeUtility.decodeText解码可能被编码的字符串。
8. 完整系统案例
-
案例一:邮件发送工具类 (支持文本、HTML、附件、图片)
import javax.mail.*; import javax.mail.internet.*; import javax.activation.*; import java.util.*; public class MailSender { private Session session; public MailSender(String host, int port, final String username, final String password, boolean useSSL, boolean useStartTLS) { Properties props = new Properties(); props.put("mail.smtp.host", host); props.put("mail.smtp.port", port); props.put("mail.smtp.auth", "true"); if (useSSL) { props.put("mail.smtp.ssl.enable", "true"); } else if (useStartTLS) { props.put("mail.smtp.starttls.enable", "true"); } this.session = Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); } public void sendTextMail(String from, String to, String subject, String text) throws MessagingException { MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); message.setSubject(subject, "UTF-8"); message.setText(text, "UTF-8"); Transport.send(message); } public void sendHtmlMail(String from, String to, String subject, String htmlContent) throws MessagingException { MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); message.setSubject(subject, "UTF-8"); message.setContent(htmlContent, "text/html; charset=UTF-8"); Transport.send(message); } public void sendMailWithAttachment(String from, String to, String subject, String text, List attachments) throws MessagingException { MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); message.setSubject(subject, "UTF-8"); MimeMultipart multipart = new MimeMultipart(); // 文本部分 MimeBodyPart textPart = new MimeBodyPart(); textPart.setText(text, "UTF-8"); multipart.addBodyPart(textPart); // 附件部分 for (Attachment attachment : attachments) { MimeBodyPart attachmentPart = new MimeBodyPart(); attachmentPart.setDataHandler(new DataHandler(new FileDataSource(attachment.getFilePath()))); attachmentPart.setFileName(MimeUtility.encodeText(attachment.getFileName(), "UTF-8", "B")); multipart.addBodyPart(attachmentPart); } message.setContent(multipart); Transport.send(message); } public void sendMailWithInlineImages(String from, String to, String subject, String htmlContent, MapcidImageMap) throws MessagingException { MimeMessage message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); message.setSubject(subject, "UTF-8"); MimeMultipart multipart = new MimeMultipart("related"); // HTML 部分 MimeBodyPart htmlPart = new MimeBodyPart(); htmlPart.setContent(htmlContent, "text/html; charset=UTF-8"); multipart.addBodyPart(htmlPart); // 图片部分 for (Map.Entry entry : cidImageMap.entrySet()) { String cid = entry.getKey(); String imagePath = entry.getValue(); MimeBodyPart imagePart = new MimeBodyPart(); imagePart.setDataHandler(new DataHandler(new FileDataSource(imagePath))); imagePart.setContentID("<" + cid + ">"); imagePart.setDisposition(MimeBodyPart.INLINE); multipart.addBodyPart(imagePart); } message.setContent(multipart); Transport.send(message); } public static class Attachment { private String filePath; private String fileName; public Attachment(String filePath, String fileName) { this.filePath = filePath; this.fileName = fileName; } // Getters... (省略) } } -
案例二:邮件接收监控服务 (IMAP IDLE/Polling) 这是一个更复杂的案例,涉及持续监控邮箱 (使用 IMAP IDLE 指令或定时轮询)。
import javax.mail.*; import javax.mail.event.MessageCountEvent; import javax.mail.event.MessageCountListener; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class MailMonitorService { private final String host; private final int port; private final String username; private final String password; private Store store; private Folder inbox; private ScheduledExecutorService scheduler; public MailMonitorService(String host, int port, String username, String password) { this.host = host; this.port = port; this.username = username; this.password = password; } public void start() throws MessagingException { Properties props = new Properties(); props.put("mail.imap.host", host); props.put("mail.imap.port", port); props.put("mail.imap.ssl.enable", "true"); Session session = Session.getInstance(props); store = session.getStore("imap"); store.connect(username, password); inbox = store.getFolder("INBOX"); inbox.open(Folder.READ_WRITE); // 需要读写权限来标记邮件 // 方式一:使用 IMAP IDLE (服务器推送新邮件通知) try { if (inbox instanceof IMAPFolder) { IMAPFolder imapFolder = (IMAPFolder) inbox; imapFolder.addMessageCountListener(new MessageCountListener() { @Override public void messagesAdded(MessageCountEvent e) { Message[] newMessages = e.getMessages(); processNewMessages(newMessages); } @Override public void messagesRemoved(MessageCountEvent e) { // 处理邮件删除事件 } }); // 进入 IDLE 模式 imapFolder.idle(); } else { throw new IllegalStateException("IDLE only supported with IMAPFolder"); } } catch (Exception e) { // 如果 IDLE 失败或不可用,回退到轮询 startPolling(); } // 方式二:定时轮询 (如果 IDLE 不可用或失败) // startPolling(); } private void startPolling() { scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(() -> { try { // 检查是否有新邮件 if (inbox.hasNewMessages()) { // 这个方法可能不可靠 // 更可靠的方式:记录上次检查的UID或邮件数量 int oldCount = ...; // 保存上次的邮件数量或最高 UID Message[] newMessages = inbox.getMessages(); // 或者根据 UID 获取新邮件 processNewMessages(newMessages); // 更新 oldCount 或 UID } } catch (MessagingException e) { e.printStackTrace(); // 处理错误,可能需要重新连接 } }, 0, 60, TimeUnit.SECONDS); // 每 60 秒检查一次 } private void processNewMessages(Message[] messages) { for (Message message : messages) { try { // 解析邮件内容 (同前面接收邮件部分) // ... System.out.println("收到新邮件: " + message.getSubject()); // 处理邮件业务逻辑... // 标记为已读 (可选) message.setFlag(Flags.Flag.SEEN, true); } catch (Exception e) { e.printStackTrace(); } } } public void stop() { try { if (inbox != null && inbox.isOpen()) { inbox.close(false); // 不删除标记为 DELETED 的邮件 } if (store != null && store.isConnected()) { store.close(); } if (scheduler != null) { scheduler.shutdown(); } } catch (MessagingException e) { e.printStackTrace(); } } } -
案例三:简单的邮件客户端 (命令行或 Swing) 这是一个较大的项目框架,涉及用户界面 (命令行菜单或 Swing GUI)、邮件列表展示、邮件查看、发送邮件等功能。核心逻辑复用前面发送和接收邮件的代码块。这里提供一个概念设计:
- 功能模块:
- 配置管理 (保存/加载多个邮箱账号配置)。
- 发送邮件界面 (输入收件人、主题、正文、添加附件)。
- 接收邮件列表 (展示收件箱邮件主题、发件人、日期)。
- 邮件查看器 (显示邮件正文、附件列表、下载附件)。
- 连接管理 (连接/断开 IMAP/POP3/SMTP)。
- 技术选型:
- 命令行: Java 控制台输入输出。
- GUI: Java Swing 或 JavaFX。
- 核心挑战: 邮件列表的实时刷新 (如使用
MailMonitorService)、富文本正文的渲染、附件的预览等。
- 功能模块:
-
案例四:Spring Boot 集成邮件发送 Spring Boot 提供了强大的邮件发送支持 (
spring-boot-starter-mail)。 依赖 (Maven):org.springframework.boot spring-boot-starter-mail配置文件 (
application.properties):spring.mail.host=smtp.example.com spring.mail.port=587 spring.mail.username=your_username@example.com spring.mail.password=your_password spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true # 可选调试 spring.mail.properties.mail.debug=true使用
JavaMailSender:import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; @Service public class EmailService { private final JavaMailSender mailSender; public EmailService(JavaMailSender mailSender) { this.mailSender = mailSender; } public void sendSimpleMail(String to, String subject, String text) { SimpleMailMessage message = new SimpleMailMessage(); message.setTo(to); message.setSubject(subject); message.setText(text); mailSender.send(message); } public void sendHtmlMail(String to, String subject, String htmlContent) throws MessagingException { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); // true for multipart helper.setTo(to); helper.setSubject(subject); helper.setText(htmlContent, true); // true indicates HTML mailSender.send(mimeMessage); } public void sendMailWithAttachment(String to, String subject, String text, File attachment) throws MessagingException { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); helper.setTo(to); helper.setSubject(subject); helper.setText(text); helper.addAttachment(attachment.getName(), attachment); mailSender.send(mimeMessage); } public void sendMailWithInlineImage(String to, String subject, String htmlContent, File image, String cid) throws MessagingException { MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); helper.setTo(to); helper.setSubject(subject); helper.setText(htmlContent, true); helper.addInline(cid, image); // cid 是 HTML 中引用的 Content-ID (不带 < >) mailSender.send(mimeMessage); } }调用示例 (在 Controller 或 Service 中):
@Autowired private EmailService emailService; public void sendWelcomeEmail(String userEmail) { try { emailService.sendHtmlMail(userEmail, "欢迎加入", "欢迎!
感谢注册...
"); } catch (MessagingException e) { // 处理异常 } }
这份指南涵盖了 Java 邮件编程的绝大部分核心内容,从基础协议、API 使用到高级技巧、安全实践和完整案例。希望它能成为你开发邮件相关应用的权威参考手册。请根据实际需求选择适用的部分进行实践。









