这是一份非常详细、实用、通俗易懂、权威且全面的 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 核心组件介绍
- Session
- Message
- Transport (发送)
- 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.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.3 发送邮件 (Transport)
- 3.4 发送纯文本邮件 (完整示例代码)
- 3.5 发送 HTML 邮件
- 3.6 发送带附件的邮件 (Multipart, MimeBodyPart)
- 3.7 发送带内嵌图片的 HTML 邮件 (Content-ID)
- 3.8 常见 SMTP 服务器配置示例 (Gmail, QQ, 163, 企业邮箱)
接收邮件 (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.2 连接到邮件存储 (Store)
- 4.3 打开邮箱文件夹 (Folder)
- 4.3.1 常见文件夹 (INBOX, Sent, Drafts, Trash)
- 4.3.2 文件夹访问模式 (READ_ONLY, READ_WRITE)
- 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 邮件回执
处理复杂邮件结构与 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 结构
安全性与最佳实践
- 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 中文乱码问题 (字符集设置)
完整系统案例
- 案例一:邮件发送工具类 (支持文本、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 的邮件)。
- Session (会话):
-
2.3 依赖引入 JavaMail API 不是标准 Java SE 的一部分,需要额外引入。
- 官方 JARs: 从 JavaMail Project 下载 javax.mail.jar。
- Maven: <dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version> <!– 请检查最新版本 –>
</dependency> - Gradle: implementation 'com.sun.mail:javax.mail:1.6.2' // 请检查最新版本
- 另外,处理邮件内容 (如解析 HTML) 可能还需要引入其他库,如 javax.activation:activation (用于 MIME 类型处理和数据源),但在较新版本中可能已包含或使用其他实现。
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) props.put("mail.smtp.ssl.enable", "true"); // 启用 SSL
- 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 基本属性: props.put("mail.smtp.host", "smtp.example.com"); // SMTP服务器地址
-
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("<h1>HTML 内容</h1><p>这是一封包含 <strong>HTML</strong> 格式的邮件。</p>", "text/html; charset=UTF-8");
- 3.2.4 设置日期: (可选,默认是当前时间) message.setSentDate(new Date());
- 3.2.5 设置邮件头: (自定义头信息,非必须) message.setHeader("X-Custom-Header", "MyValue");
- 3.2.1 设置发件人、收件人、抄送、密送: // 设置发件人 (可以是字符串或 Address 对象)
-
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("你好,\\n\\n这是一封使用 JavaMail API 发送的纯文本测试邮件。\\n\\n祝好!");// 发送邮件
Transport.send(message); // 简化发送方式 (内部处理 Transport)System.out.println("纯文本邮件发送成功!");
} catch (MessagingException e) {
throw new RuntimeException("邮件发送失败", e);
}
}
} -
3.5 发送 HTML 邮件 修改正文设置部分:
String htmlContent = "<html><body>"
+ "<h1>HTML 邮件测试</h1>"
+ "<p>这是一封<strong>HTML格式</strong>的邮件。</p>"
+ "<p>可以包含 <span style='color: red;'>样式</span> 和 <a href='https://www.example.com'>链接</a>。</p>"
+ "</body></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 = "<html><body>"
+ "<h1>包含图片的邮件</h1>"
+ "<p>图片内嵌在邮件中:</p>"
+ "<img src='cid:image1'>" // 注意 cid:image1
+ "</body></html>";
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", "<image1>"); // 设置 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: props.put("mail.pop3.host", "pop.example.com"); // 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.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("正文:\\n" + 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 正文:\\n" + partContent);
} else if (contentType.toLowerCase().contains("text/plain")) {
System.out.println("纯文本正文:\\n" + partContent);
} else {
System.out.println("其他文本内容 (" + contentType + "):\\n" + 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("正文:\\n" + 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 = "回复内容\\n\\n原邮件:\\n" + 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)。
-
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 中引用正确。
-
7.7 中文乱码问题
- 原因: 邮件头 (主题、发件人/收件人名称) 或正文未正确指定字符集;使用的字符集与邮件客户端解码字符集不一致;附件文件名编码问题。
- 解决: 在设置主题、正文、附件文件名时,显式指定正确的字符集 (通常是 UTF-8): message.setSubject("中文主题", "UTF-8"); // 设置主题和字符集
message.setText("中文正文", "UTF-8"); // 纯文本
// HTML
message.setContent("<p>中文</p>", "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<Attachment> 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, Map<String, String> cidImageMap) 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<String, String> 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):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>配置文件 (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, "欢迎加入", "<h1>欢迎!</h1><p>感谢注册…</p>");
} catch (MessagingException e) {
// 处理异常
}
}
这份指南涵盖了 Java 邮件编程的绝大部分核心内容,从基础协议、API 使用到高级技巧、安全实践和完整案例。希望它能成为你开发邮件相关应用的权威参考手册。请根据实际需求选择适用的部分进行实践。



