JavaMail 嚴格遵循 RFC 822/2822 標準,僅承認逗號(,)作為郵件頭多地址分隔符。但 Microsoft Outlook/Exchange 常以分號(;)分隔,導致 InternetAddress.parse() 拋出 AddressException 或解析出錯誤地址。本文分析標準差異,提供三種兼容方案。
RFC 822 §2.7:
#規則定義列表元素以 逗號 分隔。
destination = "To:" 1#address
JavaMail InternetAddress.parse(String, boolean) 直接實作此規則,不支援分號。
String recipients = "user1@example.com; user2@example.com; user3@example.com";
InternetAddress[] addrs = InternetAddress.parse(recipients, true);
// 拋出 AddressException: Missing final '@domain' in string ``user2@example.com; user3@example.com''
public static InternetAddress[] parseRecipients(String raw) {
if (raw == null) return new InternetAddress[0];
// 將分號統一替換為逗號,再交給標準解析器
String normalized = raw.replace(';', ',');
try {
return InternetAddress.parse(normalized, true);
} catch (AddressException e) {
throw new IllegalArgumentException("收件人格式錯誤: " + raw, e);
}
}
適用場景: 確定來源系統固定使用分號,且地址本身不含分號(郵件地址合法字符不含分號)。
複製 JDK/JavaMail 原始碼中的 InternetAddress,修改 parse() 內部的 parseAddressList(),將分號視為同等分隔符:
// 核心修改點
if (c == ',' || c == ';') { // 原本僅判斷 ','
// 分隔符處理邏輯
}
風險: 違背 RFC,可能導致與嚴格遵循標準的 MTA(如 Postfix、Gmail SMTP)交互異常;升級 JavaMail 版本需重複修改。不建議生產環境使用。
在 Spring Mail / JavaMail 發送前,以 MimeMessagePreparator 統一正規化 To、Cc、Bcc Header:
public class RecipientNormalizingPreparator implements MimeMessagePreparator {
private final MimeMessagePreparator delegate;
public RecipientNormalizingPreparator(MimeMessagePreparator delegate) {
this.delegate = delegate;
}
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
delegate.prepare(mimeMessage);
// 正規化已設置的收件人 Header
normalizeHeader(mimeMessage, "To");
normalizeHeader(mimeMessage, "Cc");
normalizeHeader(mimeMessage, "Bcc");
}
private void normalizeHeader(MimeMessage msg, String header) throws MessagingException {
String[] values = msg.getHeader(header);
if (values != null) {
for (int i = 0; i < values.length; i++) {
values[i] = values[i].replace(';', ',');
}
msg.setHeader(header, values);
}
}
}
使用時包裝原有 MimeMessagePreparator,零侵入業務代碼。
Message.getRecipients() 取得的原始 Header 字串,同樣先 replace(';', ',') 再 InternetAddress.parse()。@Test
public void testSemicolonRecipients() {
String raw = "a@a.com; b@b.com ;c@c.com";
InternetAddress[] addrs = RecipientUtil.parseRecipients(raw);
assertEquals(3, addrs.length);
assertEquals("a@a.com", addrs[0].getAddress());
assertEquals("b@b.com", addrs[1].getAddress());
assertEquals("c@c.com", addrs[2].getAddress());
}
遵循標準、源頭治理、邊界兼容,是處理郵件地址格式衝突的穩健策略。