从spring boot发邮件聊到开发的友好性
前些天帮一个朋友做网站,全站都是静态页面,唯一需要用到后端开发的是他需要一个留言板。传统的留言板一般都是提交后保存到数据库,然后提供一个后台的留言列表给管理人员看,我嫌麻烦,就决定留言提交到后台直接发邮件出去,这样就不用开发后台页面了,他也不需要登录一个什么后台才能看留言,两全其美,岂不美哉。

1、最简版spring boot发邮件
spring boot发邮件还是挺简单的,首先把发邮件的start加到pom里面:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
然后在application.properties里面配置好关于发邮件的参数
spring.mail.host=smtp.163.com
spring.mail.port=25
spring.mail.username=yourmail@163.com
spring.mail.password=yourpassword
其中spring.mail.host和spring.mail.username是一一对应的,哪里的邮箱就要用哪里的smtp服务器
然后我写了一个controller来接收留言板的内容:
@Value("${spring.mail.username}")
private String fromMail;
@Autowired
private JavaMailSender mailSender;
@RequestMapping(value = "/getNote", method = RequestMethod.POST)
public String getNote(Note note) {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(mimeMessage, true);
//发件人
helper.setFrom(fromMail,note.yourName);
//收件人(留言内容最终发往的邮箱地址)
helper.setTo("recieve@mail.com");
//标题
helper.setSubject(note.yourSubject);
//文本
helper.setText("from email:"+note.yourEmail+"\n"+note.yourMessage);
mailSender.send(mimeMessage);
} catch (MessagingException | UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "redirect:return.htm";
}
public class Note {
String yourName;
String yourEmail;
String yourSubject;
String yourMessage;
//getter,setter省略
}
- note对象是留言板的内容
- JavaMailSender和MimeMessageHelper是官方推荐的好基友,一般配套使用
- fromMail就是在application.properties里面配置的spring.mail.username属性,也就是发邮件的一方,用helper.setTo(...)配置
- 额外说一下,由于填留言板时一般也会留邮件地址,但那个邮件地址和这里的任何一个设置邮件的地方毫无关系,只需要在邮件的文本里面做记录就好了,我之前就弱智了一把,把留言板里面的邮件地址填到helper.setTo(...),结果浪费了我半个小时查这种bug,真是一言难尽...
2、授权码发邮件
很多时候,按照上面的方法发邮件会出现下面的错误:
javax.mail.AuthenticationFailedException: 535 Error: authentication failed
其中的一个原因是邮件服务器使用了授权码登录方式,也就是第三方登录不能直接使用账号密码,而需要使用一种授权码的方式,比如上面的163.com邮箱,就可以设置授权码登录,设置界面如下:

QQ邮箱更是默认就需要用授权码登录,QQ邮箱的授权登录操作文档请看这里
使用授权码登录后,需要把之前application.properties里面spring.mail.password的内容从密码换成授权码,就能够正常发邮件了。
3、简单聊下JavaMailSender和MimeMessageHelper
我们查下JavaMailSender的代码就知道,它其实背景比较复杂。首先它继承了org.springframework.mail.MailSender接口。
public interface JavaMailSender extends MailSender {
...
}
而它本身也是一个接口,实现类只有一个,JavaMailSenderImpl

我们再来翻JavaMailSenderImpl的代码,发现spring并没有自己来做发送邮件的功能,而是直接用了java自身的邮件发送功能,核心是这一段
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++) {
// 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);
}
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);
}
}
}
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);
}
}
doSend方法中调用的核心类就是Transport类,这个类的包名是javax.mail。spring不愧是集成大师,java自带的mail功能经过spring的标准化包装就成了spring自身功能的一部分,再通过spring boot的包装,用starter的方式再次做简化,我们就能够直接通过极简的方式使用了。
当然,简化的方法多种多样,另外的一种形式的包装就是使用helper类的方法,spring使用的就是MimeMessageHelper。在javax.mail在处理邮件的方式上,使用的是分而治之的办法,不同的类处理不同的问题,所以看到很多的类在处理各种问题和情况。


这种做法在实现功能上是很好的,把一个复杂的问题分解成若干个小问题,分别实现。但对使用的开发人员就谈不上友好了,容易出现以下几个问题:
- 不直观,调用者不知道从何下手
- 查找麻烦,类太多而且功能分散,不容易找到对应的功能类
- 关系复杂,经常对要引用哪个类会很没有把握,因为太多处理单一问题的类
- 没有统一的入口,上手难,很难脱离文档直接使用
针对上述情况,spring通过MimeMessageHelper,把几乎所有邮件发送需要处理的问题就集中到了这个类里面,使用方便又好找。下面是这个类所有的方法。



这个类不可谓不复杂,基本上涵盖了所有发邮件方面的功能,但因为都集成在一个类里面,非常方便好用,可以说是一个对程序员友好的典范了,值得大家在开发时做借鉴。
从spring boot发邮件聊到开发的友好性的更多相关文章
- 1 分钟教会你用 Spring Boot 发邮件
Spring Boot 提供了一个发送邮件的简单抽象,使用的是下面这个接口. org.springframework.mail.javamail.JavaMailSender Spring Boot ...
- spring boot 发邮件
报错: Mail server connection failed; nested exception is javax.mail.MessagingException: Could not con ...
- Spring Boot 的Maven多模块开发web项目使用外部容器进行部署
Spring Boot中自带有Tomcat容器,因此Spring Boot项目只需要运行main函数,就可以运行,但是以往的web项目,我们习惯于使用自己安装的Tomcat运行或者使用Tomcat.J ...
- spring boot + vue + element-ui全栈开发入门——开篇
最近经常看到很多java程序员朋友还在使用Spring 3.x,Spring MVC(struts),JSP.jQuery等这样传统技术.其实,我并不认为这些传统技术不好,而我想表达的是,技术的新旧程 ...
- spring boot + vue + element-ui全栈开发入门——基于Electron桌面应用开发
前言 Electron是由Github开发,用HTML,CSS和JavaScript来构建跨平台桌面应用程序的一个开源库. Electron通过将Chromium和Node.js合并到同一个运行时环 ...
- spring boot + vue + element-ui全栈开发入门
今天想弄弄element-ui 然后就在网上找了个例子 感觉还是可以用的 第一步是完成了 果断 拿过来 放到我这里这 下面直接是连接 点进去 就可以用啊 本想着不用vue 直接导入连接 ...
- Spring Boot + Mybatis + Redis二级缓存开发指南
Spring Boot + Mybatis + Redis二级缓存开发指南 背景 Spring-Boot因其提供了各种开箱即用的插件,使得它成为了当今最为主流的Java Web开发框架之一.Mybat ...
- spring boot发简单文本邮件
首先要去邮箱打开POP3/SMTP权限: 然后会提供个授权码,用来发送邮件.忘记了,可以点生成授权码再次生成. 1.引入spring boot自带的mail依赖,这里版本用的:<spring-b ...
- Spring Boot整合邮件发送
概述 Spring Boot下面整合了邮件服务器,使用Spring Boot能够轻松实现邮件发送:整理下最近使用Spring Boot发送邮件和注意事项: Maven包依赖 <dependenc ...
随机推荐
- markdown 入门教程(完整版)
Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式. 1. 标题 Markdown支持6种级别的标题,对应html标签 h1 ~ h6 ...
- redis is configured to save RDB snapshots
Redis被配置为保存数据库快照,但它目前不能持久化到硬盘.用来修改集合数据的命令不能用 原因: 强制关闭Redis 快照导致不能持久化. 解决方法: 运行 config set stop-write ...
- Python项目生成所有依赖包的清单
最近写完的自动化脚本,分享给同事的时候发现依赖包很难解决(使用的不是virtualenv环境).想起来之前看开源接口平台项目的时候可以一键下载依赖包,于是就找到了第三方包pipreqs,可以自动帮助我 ...
- Netty 入门,这一篇文章就够了
Netty是Java领域有名的开源网络库,特点是高性能和高扩展性,因此很多流行的框架都是基于它来构建的,比如我们熟知的Dubbo.Rocketmq.Hadoop等,针对高性能RPC,一般都是基于Net ...
- Python 加密 shellcode 免杀
Python 加密 shellcode 免杀 环境准备: Windows7 32 位系统: Shellcode 使用 kali linux Metasploit 生成 shellcode Wind ...
- NOIP模拟 29
T1第一眼觉得是网络流 看见4e6条边200次增广我犹豫了 O(n)都过不去的赶脚.. 可是除了网络流板子我还会什么呢 于是交了个智障的EK 还是用dijkstra跑的 居然有50分!$(RP--)$ ...
- 大数据之路day01_2--记事本与EditPlus编写Hello World并且运行
在上一节我们成功的安装JAVA并且将其环境配置成功,接下来我们来编写第一个java程序——Hello World 1.利用记事本编写代码,利用命令行来编译运行 (1)新建记事本,(文件名).java后 ...
- JS面试题-<变量和类型>-JavaScript的数据类型
前言 整理以前的面试题,发现问js数据类型的频率挺高的,回忆当初自己的答案,就是简简单单的把几个类型名称罗列了出来,便没有了任何下文.其实这一个知识点下可以牵涉发散出很多的知识点,如果一个面试者只是罗 ...
- Java升级那么快,多个版本如何灵活切换和管理?
前言 近两年,Java 版本升级频繁,感觉刚刚掌握 Java8,写本文时,已听到 java14 的消息,无论是尝鲜新特性(Java12 中 Collectors.teeing 超强功能使用),还是由于 ...
- 详解JavaScript错误捕获和上报流程
怎么捕获错误并且处理,是一门语言必备的知识.在JavaScript中也是如此. 那怎么捕获错误呢?初看好像很简单,try-catch就可以了嘛!但是有的时候我们发现情况却繁多复杂. Q1: 同步可以t ...