springboot 邮件发送太慢的问题定位及解决方案

次元: 365bet娱乐注册 时间戳: 2025-10-23 12:44:07 观察者: admin 访问量: 5568 能量值: 798
springboot 邮件发送太慢的问题定位及解决方案

最近有个小功能,需要发送邮件。

使用了spring-boot-starter-mail组件进行邮件操作,却发现速度特别慢,每次发送都要20秒左右。为了解决这个问题,针对源码进行了一些研究和优化

org.springframework.boot

spring-boot-starter-mail

问题定位

通过分析,发现主要在以下两部分比较耗时

创建连接:查看源码发现org.springframework.mail.javamail.JavaMailSenderImpl的doSend方法 每次发送邮件都会创建新的连接。每次创建连接都要10秒左右

protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException {

Map failedMessages = new LinkedHashMap<>();

Transport transport = null;

try {

for (int i = 0; i < mimeMessages.length; i++) {

// Check transport connection first...

if (transport == null || !transport.isConnected()) {

if (transport != null) {

try {

transport.close();

}

catch (Exception ex) {

// Ignore - we're reconnecting anyway

}

transport = null;

}

try {

transport = connectTransport();

}

catch (AuthenticationFailedException ex) {

throw new MailAuthenticationException(ex);

}

...

生成消息ID: 这里耗时其实是没想到的,通过查看源码发现在生成消息ID的时候,代码中会尝试去DNS服务查找跟自身IP符合的域名,这个操作特别耗时

解决方案

定位到了问题,解决方案自然也就有了

缓存TCP连接或池化

针对第一个问题,最常见的方式就是将Transport对象池化。感兴趣的可以自己去实现,通过common-pool就可以轻松实现了,

由于我的功能更简单,单个资源就够用了,所有就做了下面这种直接缓存连接对象即可最简单的优化,代码如下

package com.leewan.server.service.impl;

import jakarta.mail.Address;

import jakarta.mail.AuthenticationFailedException;

import jakarta.mail.Transport;

import jakarta.mail.internet.MimeMessage;

import org.springframework.mail.MailAuthenticationException;

import org.springframework.mail.MailException;

import org.springframework.mail.MailSendException;

import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.util.Date;

import java.util.LinkedHashMap;

import java.util.Map;

public class MailSenderImpl extends JavaMailSenderImpl {

private static final String HEADER_MESSAGE_ID = "Message-ID";

private Transport transport;

@Override

protected synchronized void doSend(MimeMessage[] mimeMessages, Object[] originalMessages) throws MailException {

Map failedMessages = new LinkedHashMap<>();

for (int i = 0; i < mimeMessages.length; i++) {

// Check transport connection first...

if (transport == null || !transport.isConnected()) {

try {

transport = connectTransport();

}

catch (AuthenticationFailedException ex) {

throw new MailAuthenticationException(ex);

}

catch (Exception ex) {

// Effectively, all remaining messages failed...

for (int j = i; j < mimeMessages.length; j++) {

Object original = (originalMessages != null ? originalMessages[j] : mimeMessages[j]);

failedMessages.put(original, ex);

}

throw new MailSendException("Mail server connection failed", ex, failedMessages);

}

}

// Send message via current transport...

MimeMessage mimeMessage = mimeMessages[i];

try {

if (mimeMessage.getSentDate() == null) {

mimeMessage.setSentDate(new Date());

}

String messageId = mimeMessage.getMessageID();

mimeMessage.saveChanges();

if (messageId != null) {

// Preserve explicitly specified message id...

mimeMessage.setHeader(HEADER_MESSAGE_ID, messageId);

}

Address[] addresses = mimeMessage.getAllRecipients();

transport.sendMessage(mimeMessage, (addresses != null ? addresses : new Address[0]));

}

catch (Exception ex) {

Object original = (originalMessages != null ? originalMessages[i] : mimeMessage);

failedMessages.put(original, ex);

}

}

if (!failedMessages.isEmpty()) {

throw new MailSendException(failedMessages);

}

}

}

配置类如下

package com.leewan.server.context.mail;

import com.leewan.server.service.impl.MailSenderImpl;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

import org.springframework.boot.autoconfigure.mail.MailProperties;

import org.springframework.boot.context.properties.EnableConfigurationProperties;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.mail.javamail.JavaMailSender;

import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.util.Map;

import java.util.Properties;

@Configuration

@EnableConfigurationProperties(MailProperties.class)

public class MailConfiguration {

@Bean

@ConditionalOnMissingBean(JavaMailSender.class)

MailSenderImpl mailSender(MailProperties properties) {

MailSenderImpl sender = new MailSenderImpl();

applyProperties(properties, sender);

return sender;

}

private void applyProperties(MailProperties properties, JavaMailSenderImpl sender) {

sender.setHost(properties.getHost());

if (properties.getPort() != null) {

sender.setPort(properties.getPort());

}

sender.setUsername(properties.getUsername());

sender.setPassword(properties.getPassword());

sender.setProtocol(properties.getProtocol());

if (properties.getDefaultEncoding() != null) {

sender.setDefaultEncoding(properties.getDefaultEncoding().name());

}

if (!properties.getProperties().isEmpty()) {

sender.setJavaMailProperties(asProperties(properties.getProperties()));

}

}

private Properties asProperties(Map source) {

Properties properties = new Properties();

properties.putAll(source);

return properties;

}

}

设置属性避免DNS查找

通过源码发现可以设置mail.mime.address.usecanonicalhostname去控制是否去使用DNS查询域名。

那么这种情况 我们只需要在启动命令加上-D参数就行,如下

java -Dmail.mime.address.usecanonicalhostname=false -Dfile.encoding=UTF8 -Duser.timezone=GMT+8 -jar app.jar

总结

经过以上两手优化,邮件发送速度,从原来的20秒,现在只需要0.5秒左右

相关维度

伊莱克斯电烤箱怎么样 伊莱克斯电烤箱质量好吗

伊莱克斯电烤箱怎么样 伊莱克斯电烤箱质量好吗

刚买回来的冰箱多久可以插电,新冰箱要24小时才能插电吗

刚买回来的冰箱多久可以插电,新冰箱要24小时才能插电吗

雞八毛是什麼意思?

雞八毛是什麼意思?

Find More Calculator☟

Find More Calculator☟