打开邮箱,读取现有和启动 MessageCountListener 时的 Javamail 同步

Javamail synchronization when opening mailbox, reading existing and starting MessageCountListener

在我看来,使用 Javamail 打开 IMAP 邮箱、阅读现有的未读邮件并添加一个 MessageChangedListener 事件侦听器以阅读后续新到达的邮件并没有万无一失的方法。

我正在编写的应用程序必须只处理每条消息一次,并且绝不会错过任何电子邮件。

这是监听器:

public class EmailListener implements MessageCountListener {

    private final IncomingEmailProcessor processor;

    @Override
    public void messagesAdded(final MessageCountEvent event) {
        for (Message email : event.getMessages()) {
                processor.process(email);
        }
    }
}

如果我先添加监听器,然后像这样抓取所有未读消息:

Session session = javax.mail.Session.getInstance(imapProperties);
store = (IMAPStore) session.getStore(imapProtocol);
store.connect(imapHost, imapUser, imapPassword);
inbox = (IMAPFolder) store.getFolder("INBOX");
inbox.open(Folder.READ_WRITE);
inbox.addMessageCountListener(emailListener);
Message messages[] = inbox.search(
    new FlagTerm(new Flags(Flags.Flag.SEEN), false));
for (Message message : messages) {
    processor.process(message);
}
while (inbox.isOpen()) {
    inbox.idle();
}

然后在启动侦听器和获取未读消息之间有可能收到一封新电子邮件,并且会被阅读两次。

如果我改变它并在从搜索中获取未读消息后添加监听器,那么有可能在获取未读消息后但在添加监听器之前收到一封新电子邮件,所以它会得到错过了!

这个问题有解决办法吗?

打开文件夹,处理文件夹内的所有邮件,查看是否有新邮件到达(邮件数增加)。如果是这样,循环。如果没有,则添加监听器,然后等待新消息(例如,通过调用 idle 方法)。

了解其工作原理的部分关键是了解何时允许服务器通知客户端新消息,以及 JavaMail 何时会看到这些通知。在获取消息数量和添加侦听器之间,JavaMail 不会看到任何新消息通知。当您执行允许 JavaMail 看到通知的操作时,侦听器将就位。

JavaMail 常见问题解答包括 code example

我遇到了类似的问题。这是我根据 Bill 的回答得出的结果(对于那些正在寻找完整示例的人):

 public static void loadUnreadEmails() throws MessagingException, IOException, SQLException {

    Store store = null;
    Properties props = new Properties();
    props.setProperty("mail.store.protocol", "imaps");
    try {

        Session session = Session.getInstance(props, null);
        store = session.getStore();
        store.connect("imap.gmail.com", EMAIL_ID,
                EMAIL_PASSWORD);
        Folder inbox = store.getFolder("INBOX");
        inbox.open(Folder.READ_WRITE);
        int msgCount;
        do {//go through all the unread emails in the inbox at least once
            msgCount = inbox.getMessageCount();//set how many messages are in the inbox when the array is created
            // Fetch unseen messages from inbox folder
            javax.mail.Message[] messages = inbox.search(
                    new FlagTerm(new Flags(Flags.Flag.SEEN), false));
            for (javax.mail.Message msg : messages) {
                //process emails here
                processEmail(msg);
            }
            //if a new message came in while reading the messages start the loop over and get all unread messages    
        } while (inbox.getMessageCount() != msgCount);
        //add listener
        inbox.addMessageCountListener(new MessageCountAdapter() {
            @Override
            public void messagesAdded(MessageCountEvent ev) {
                javax.mail.Message[] messages = ev.getMessages();
                for (javax.mail.Message msg : messages) {
                    //process emails here
                    processEmail(msg);
                }
            }
        });
        // wait for new messages
        while (inbox.isOpen()) {                
            //every 25 minutes poke the server with a inbox.getMessageCount() to keep the connection active/open
            ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
            final Runnable pokeInbox = () -> {
                try {
                    inbox.getMessageCount();
                } catch (MessagingException ex) {
                    //nothing doin'
                }
            };
            scheduler.schedule(pokeInbox, 25, TimeUnit.MINUTES);
            ((IMAPFolder) inbox).idle();
        }
    } catch (FolderClosedException e) {
        e.printStackTrace();
        System.out.println("error connection was dropped");
        if (store != null) {
            store.close();
        }
        loadUnreadEmails();//restarts listening for email if the connection times out
    } finally {
        if (store != null) {
            store.close();
        }
    }
}