使用spring boot通常使用spring-boot-starter-mail进行邮件的发送。当进行邮件群发的话,如果一个收件人的地址错误,会导致所有邮件都发送失败。因此我们需要在邮件发送失败的时候把错误的收件人移除,重新发送。

  当邮件发送失败的时候会抛出MailSendException,异常信息中包含错误的收件人信息。

  主要代码如下:

private void sendMail(List<String> mailList, MimeMessageHelper message){
try{
this.mailSender.send(message.getMimeMessage());
}catch (MailSendException e){
Set<String> tmpInvalidMails = getInvalidAddress(e);
// 非无效收件人导致的异常,暂不处理
if (tmpInvalidMails.isEmpty()){
logger.error(e.getMessage());
return;
}
mailList.removeAll(tmpInvalidMails);
if(mailList.isEmpty()){
logger.error("邮件发送失败,无收件人" + e.getMessage());
return;
}
message.setTo(mailList.toArray(new String[0]));
sendMail(mailList, message)
}
}

  捕获邮件发送失败的异常,首先判断是否是收件人无效导致的异常。从异常中解析无效收件人,收件人例表中移除无效的收件人,重新发送邮件。

  如何从从异常中获取无效收件人,首先看下JavaMailSenderImpl这个类的doSend方法

 protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException {
Map<Object, Exception> failedMessages = new LinkedHashMap<>();
Transport transport = null;
try {
for (int i = 0; i < mimeMessages.length; i++) {
...
// Send message via current transport...
MimeMessage mimeMessage = mimeMessages[i];
try {
...
}
catch (Exception ex) {
Object original = (originalMessages != null ? originalMessages[i] : mimeMessage);
failedMessages.put(original, ex);
}
}
}
finally {
try {
if (transport != null) {
transport.close();
}
}
catch (Exception ex) {
if (!failedMessages.isEmpty()) {
throw new MailSendException("Failed to close server connection after message failures", ex,
failedMessages);
}
else {
throw new MailSendException("Failed to close server connection after message sending", ex);
}
}
} if (!failedMessages.isEmpty()) {
throw new MailSendException(failedMessages);
}
}

  当邮件发送过程中遇到异常会保存到failedMessages中,我们需要从中解析收件人无效导致的异常。

  接着继续看源码SMTPTransport的rcptTo方法,会去校验每个收件人,通过向服务器发送RCPT TO:<地址>,根据响应码来判断收件人是否有效。

protected void rcptTo() throws MessagingException {
List<InternetAddress> valid = new ArrayList();
List<InternetAddress> validUnsent = new ArrayList();
List<InternetAddress> invalid = new ArrayList();
...
int k;
for(k = 0; k < this.addresses.length; ++k) {
sfex = null;
InternetAddress ia = (InternetAddress)this.addresses[k];
String cmd = "RCPT TO:" + this.normalizeAddress(ia.getAddress());
if (dsn) {
cmd = cmd + " NOTIFY=" + notify;
} this.sendCommand(cmd);
int retCode = this.readServerResponse();
switch(retCode) {
case 250:
case 251:
valid.add(ia);
...
break;
case 450:
case 451:
case 452:
case 552:
...
validUnsent.add(ia);
...
break;
case 501:
case 503:
case 550:
case 551:
case 553:
...
invalid.add(ia);
...
break;
default:
if (retCode >= 400 && retCode <= 499) {
validUnsent.add(ia);
} else {
...
invalid.add(ia);
}
...
}
} if (sendPartial && valid.size() == 0) {
sendFailed = true;
} int lrc;
if (sendFailed) {
this.invalidAddr = new Address[invalid.size()];
invalid.toArray(this.invalidAddr);
this.validUnsentAddr = new Address[valid.size() + validUnsent.size()];
k = 0; for(lrc = 0; lrc < valid.size(); ++lrc) {
this.validUnsentAddr[k++] = (Address)valid.get(lrc);
} for(lrc = 0; lrc < validUnsent.size(); ++lrc) {
this.validUnsentAddr[k++] = (Address)validUnsent.get(lrc);
}
}
... if (sendFailed) {
this.logger.fine("Sending failed because of invalid destination addresses");
...
throw new SendFailedException("Invalid Addresses", (Exception)mex, this.validSentAddr, this.validUnsentAddr, this.invalidAddr);
}
}

  当收件人无效发送失败会抛出SendFailedException异常,异常中包含收件人是否有效的信息。

  因此我们只要从failedMessages查找是否含有SendFailedException,然后从SendFailedException直接得到无效的收件人信息。代码如下:

    private Set<String> getInvalidAddress(MailSendException e){
Set<String> mails = new HashSet<>();
for(Exception exception: e.getFailedMessages().values()){
if(exception instanceof SendFailedException){
for(Address address: ((SendFailedException) exception).getInvalidAddresses()){
mails.add(address.toString());
}
}
}
return mails;
}

  

解决spring boot JavaMailSender部分收件人错误导致发送失败的问题的更多相关文章

  1. 解决Spring Boot 从1.x升级到 2.x 后 单点登陆(SSO)问题

    解决Spring Boot 从1.x升级到 2.x 后 单点登陆(SSO)问题   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring S ...

  2. 解决spring boot启动报错java.lang.NoClassDefFoundError: ch/qos/logback/classic/Level

    解决spring boot启动报错java.lang.NoClassDefFoundError: ch/qos/logback/classic/Level 学习了:https://blog.csdn. ...

  3. spring boot ---web应用开发-错误处理

    一.错误的处理 方法一:Spring Boot 将所有的错误默认映射到/error, 实现ErrorController @Controller @RequestMapping(value = &qu ...

  4. 如何解决spring boot 项目导入依赖后代码报错问题

    如何解决spring boot 项目导入依赖后代码报错问题 2020-08-15  14:17:18 代码截图如图所示(由于本人问题已经解决,没来得及截图,所以在网上找了一张图片)

  5. 解决一次gitlab因异常关机导致启动失败

    解决一次gitlab因异常关机导致启动失败 目录 解决一次gitlab因异常关机导致启动失败 1. 服务器异常关机 2. gitlab服务 2.1 进入gitlab容器内部 2.2 检查gitlab各 ...

  6. spring boot:方法中使用try...catch导致@Transactional事务无效的解决(spring boot 2.3.4)

    一,方法中使用try...catch导致@Transactional事务无效的解决方法 1,问题的描述: 如果一个方法添加了@Transactional注解声明事务, 而方法内又使用了try catc ...

  7. 解决spring boot中rest接口404,500等错误返回统一的json格式

    在开发rest接口时,我们往往会定义统一的返回格式,列如: { "status": true, "code": 200, "message" ...

  8. spring boot 下 500 404 错误页面处理

    spring boot 作为微服务的便捷框架,在错误页面处理上也有一些新的处理,不同于之前的spring mvc 500的页面处理是比较简单的,用java config或者xml的形式,定义如下的be ...

  9. 解决Spring boot中读取属性配置文件出现中文乱码的问题

    问题描述: 在配置文件application.properties中写了 server.port=8081 server.servlet.context-path=/boy name=张三 age=2 ...

随机推荐

  1. Software-Defined Networking:A Comprehensive Survey--Day2

    Software-Defined Networking:A Comprehensive Survey (续+1s) IV. SOFTWARE-DEFINED NETWORKS: BOTTOM-UP S ...

  2. php四排序-冒泡排序

      算法和数据结构是一个编程工作人员的内功,技术牛不牛,一般都会看这两点.作为php程序员, 提升技能当然也得学习算法. 下面介绍四种入门级排序算法: 冒泡排序.选择排序.插入排序.快速排序.   一 ...

  3. du 显示 前面以及或者两级的目录大小的方法

    mark 一下 du -h --max-depth=

  4. Python面向对象高级编程:@property--把方法变为属性

    为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数: >>> ...

  5. 微信小程序初窥-环境搭建

    关于微信小程序的背景知识,在此不做阐述,可以自行搜索了解.本文将介绍微信小程序的账号的注册,IDE的下载,创建一个实例小程序. 1.注册小程序 前去链接:https://mp.weixin.qq.co ...

  6. Http建立连接的方式

    1.协议简介 Http 协议:应用层协议 TCP 协议:传输层协议,主要解决如何在IP层之上可靠的传递数据包,使在网络上的另一端收到发端发出的所有包,并且顺序与发出的顺序一致,TCP具有可靠,面向连接 ...

  7. idea导入eclipse中的maven项目

    1.  删除项目当中除src和pom.xml文件之外的文件 2.  打开idea,选择file – new – project from existing sources 3.  选择项目路径,然后n ...

  8. c++11 decltype

    c++11 decltype decltype实际上有点像auto的反函数,auto可以让你声明一个变量,而decltype则可以从一个变量或表达式中得到类型.decltype在C++11标准制定时引 ...

  9. 自学huawei之路-AC6005-8AP添加授权码

    返回自学Huawei之路 自学huawei之路-AC6005-8AP添加授权码

  10. 【贪心策略】渡河(river)

    “假舟楫者,非能水也,而绝江河.”这句话说的是,借助渡船的人,不是会游水,却能横渡江河. 会游水的人反而不一定能顺利地横渡江河.由于江面风浪很大,他们必须潜泳渡河.这就必须用到氧气瓶.氧气瓶当然是出题 ...