首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JavaMail 解析分號分隔收件人失敗:RFC 822 標準衝突與兼容方案

     編輯:Java 相關知識

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);
    }
}

適用場景: 確定來源系統固定使用分號,且地址本身不含分號(郵件地址合法字符不含分號)。

方案二:自訂 Tokenizer 繼承 InternetAddress(源碼級修正)

複製 JDK/JavaMail 原始碼中的 InternetAddress,修改 parse() 內部的 parseAddressList(),將分號視為同等分隔符:

// 核心修改點
if (c == ',' || c == ';') {  // 原本僅判斷 ','
    // 分隔符處理邏輯
}

風險: 違背 RFC,可能導致與嚴格遵循標準的 MTA(如 Postfix、Gmail SMTP)交互異常;升級 JavaMail 版本需重複修改。不建議生產環境使用。

方案三:MimeMessage 預處理 Header(框架層攔截)

在 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,零侵入業務代碼。

最佳實踐建議

  1. 前端輸入階段統一: UI 組件(如 Select2、TagInput)回傳陣列,後端組裝時用逗號拼接,源頭消除分號。
  2. 接收外部郵件時: 若需解析 Message.getRecipients() 取得的原始 Header 字串,同樣先 replace(';', ',') 再 InternetAddress.parse()。
  3. 文件化協議: 對接第三方系統時,在 API 協議中明確規定「收件人列表以逗號分隔」,拒絕分號格式。

驗證測試用例

@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());
}

遵循標準、源頭治理、邊界兼容,是處理郵件地址格式衝突的穩健策略。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved