文档章节

初步实现 Mail 插件 —— 发送邮件

黄勇
 黄勇
发布于 2013/11/24 22:09
字数 1686
阅读 3617
收藏 53

本文是《轻量级 Java Web 框架架构设计》的系列博文。

在 Java 应用系统中为了实现邮件发送与收取功能,往往都会选择使用 JavaMail API。但该 API 涉及的内容比较繁琐,概念与细节都比较多,比如:Session、Message、Address、Authenticator、Transport、Store、Folder 等这些类,要想使用 JavaMail API,首先就要知道这些类究竟是干什么的。我并不想庖丁解牛,因为已经有太多的专家讲得比我好了,大家可充分利用搜索引擎来获取知识。

如果您也和我一样,都属于实践派,那么强烈推荐您阅读《使用 JavaMail 实现邮件发送与收取》,再加上您的聪明才智,我想 JavaMail 很快就能被您把玩在手。

如果您也和我一样,爱好框架设计,我敢打赌,您一定会想办法将 JavaMail 做一个封装,让它更加好用,而不是裸漏在外面给开发人员捣腾。

好了,下文便是 Smart Mail 插件开发过程。

在开发前,我找了许多选型,包括:Apache Commons EmailJodd Email,它们都是比较优秀的 JavaMail 封装,从本质上讲,它们都是类库(Library),而不是框架(Framework)。然而 Smart Mail 插件也是 Library,只不过它更加轻量级,它可以自由在 Smart 框架中使用,当然也可以在其他框架中使用。经过全面对比,最终我选择了 Apache Commons Email,随后要做的事情就是,再进行一次封装。Java 就是这样,封装、封装、再封装。

Smart Mail 插件包括两个功能:

  1. 发送邮件
  2. 收取邮件

先看看发送邮件如何实现吧。

发送邮件其实分为两种,一种是发送纯文本邮件,另一种是发送 HTML 邮件。我想这两种邮件都会在平时的业务需求中出现,所以将其做了一个区分,还是很有必要的,不必杀鸡用牛刀了,具体情况灵活运用。我首先想到的是,非常有必要给这两类发送方式进行一个抽象,然后通过两个具体类进行实现。

第一步:创建一个抽象的 MailSender 类

 

public abstract class MailSender {

    private static final Logger logger = Logger.getLogger(MailSender.class);

    // 创建 Email 对象(在子类中实现)
    private final Email email = createEmail();

    // 定义发送邮件的必填字段
    private final String subject;
    private final String content;
    private final String[] to;

    public MailSender(String subject, String content, String[] to) {
        this.subject = subject;
        this.content = content;
        this.to = to;
    }

    public void addCc(String[] cc) {
        try {
            if (ArrayUtil.isNotEmpty(cc)) {
                for (String address : cc) {
                    email.addCc(MailUtil.encodeAddress(address));
                }
            }
        } catch (EmailException e) {
            logger.error("错误:添加 CC 出错!", e);
        }
    }

    public void addBcc(String[] bcc) {
        try {
            if (ArrayUtil.isNotEmpty(bcc)) {
                for (String address : bcc) {
                    email.addBcc(MailUtil.encodeAddress(address));
                }
            }
        } catch (EmailException e) {
            logger.error("错误:添加 BCC 出错!", e);
        }
    }

    public void addAttachment(String path) {
        try {
            if (email instanceof MultiPartEmail) {
                MultiPartEmail multiPartEmail = (MultiPartEmail) email;
                EmailAttachment emailAttachment = new EmailAttachment();
                emailAttachment.setURL(new URL(path));
                emailAttachment.setName(path.substring(path.lastIndexOf("/") + 1));
                multiPartEmail.attach(emailAttachment);
            }
        } catch (MalformedURLException e) {
            logger.error("错误:创建 URL 出错!", e);
        } catch (EmailException e) {
            logger.error("错误:添加附件出错!", e);
        }
    }

    public final void send() {
        try {
            // 判断协议名是否为 smtp(暂时仅支持 smtp,未来可考虑扩展)
            if (!MailConstant.Sender.PROTOCOL.equalsIgnoreCase("smtp")) {
                logger.error("错误:不支持该协议!目前仅支持 smtp 协议");
                return;
            }
            // 判断是否支持 SSL 连接
            if (MailConstant.Sender.IS_SSL) {
                email.setSSLOnConnect(true);
            }
            // 设置 主机名 与 端口号
            email.setHostName(MailConstant.Sender.HOST);
            email.setSmtpPort(MailConstant.Sender.PORT);
            // 判断是否进行身份认证
            if (MailConstant.Sender.IS_AUTH) {
                email.setAuthentication(MailConstant.Sender.AUTH_USERNAME, MailConstant.Sender.AUTH_PASSWORD);
            }
            // 判断是否开启 Debug 模式
            if (MailConstant.IS_DEBUG) {
                email.setDebug(true);
            }
            // 设置 From 地址
            if (StringUtil.isNotEmpty(MailConstant.Sender.FROM)) {
                email.setFrom(MailUtil.encodeAddress(MailConstant.Sender.FROM));
            }
            // 设置 To 地址
            for (String address : to) {
                email.addTo(MailUtil.encodeAddress(address));
            }
            // 设置主题
            email.setSubject(subject);
            // 设置内容(在子类中实现)
            setContent(email, content);
            // 发送邮件
            email.send();
        } catch (Exception e) {
            logger.error("错误:发送邮件出错!", e);
        }
    }

    protected abstract Email createEmail();

    protected abstract void setContent(Email email, String content) throws MalformedURLException, EmailException;
}

注意其中的 send 方法,它就是用来发送邮件的,发送邮件的具体步骤都写在这个方法中。在构造器中初始化必备字段,除了提供几个 addXxx 方法外(如 addCc、addBcc、addAttachment),还提供了两个 abstract 方法,它们就是由子类实现的。这里用到了什么设计模式?——没错!正是模板方法模式(Template Method)。

第二步:提供两种具体实现

以下是纯文本邮件发送具体实现:

 

public class TextMailSender extends MailSender {

    public TextMailSender(String subject, String content, String[] to) {
        super(subject, content, to);
    }

    @Override
    protected Email createEmail() {
        return new MultiPartEmail();
    }

    @Override
    protected void setContent(Email email, String content) throws MalformedURLException, EmailException {
        email.setMsg(content);
    }
}

您没有看错,就这么一点,因为最核心的逻辑都放在它的父类中了。但您知道,运行时真正起作用的不是父类,而是子类,这是什么原理?——没错!多态。

以下是 HTML 邮件发送具体实现:

public class HtmlMailSender extends MailSender {

    public HtmlMailSender(String subject, String content, String[] to) {
        super(subject, content, to);
    }

    @Override
    protected Email createEmail() {
        return new ImageHtmlEmail();
    }

    @Override
    protected void setContent(Email email, String content) throws MalformedURLException, EmailException {
        ImageHtmlEmail imageHtmlEmail = (ImageHtmlEmail) email;
        imageHtmlEmail.setDataSourceResolver(new DataSourceUrlResolver(new URL("http://"), true));
        imageHtmlEmail.setHtmlMsg(content);
    }
}

看起来与纯文本邮件类似,只不过 HTML 邮件还支持在邮件内容中带有图片。

第三步:发送邮件测试

不妨通过 JUnit 单元测试对邮件发送进行验证吧。

以下是发送纯文本邮件:

public class SendTextMailTest {

    private static final String subject = "测试";
    private static final String content = "欢迎使用 Smart Framework!";
    private static final  String[] to = {"jack<jack@xxx.com>"};

    @Test
    public void sendTest() {
        MailSender mailSender = new TextMailSender(subject, content, to);
        mailSender.addAttachment("http://www.oschina.net/img/logo_s2.png");
        mailSender.send();
        System.out.println("发送完毕!");
    }
}

以上除了发送纯文本的正文以外,还发送了一个图片附件。

发送成功,以下是收到的邮件:

以下是发送 HTML 邮件:

public class SendHtmlMailTest {

    private static final String subject = "测试";
    private static final String content = "" +
        "<p><a href='http://my.oschina.net/huangyong/blog/158380'>欢迎使用 Smart Framework!</a></p>" +
        "<p><a href='http://my.oschina.net/huangyong'><img src='http://static.oschina.net/uploads/user/111/223750_100.jpg'></a></p>";
    private static final String[] to = {"jack<jack@xxx.com>"};

    @Test
    public void sendTest() {
        MailSender mailSender = new HtmlMailSender(subject, content, to);
        mailSender.addAttachment("http://www.oschina.net/img/logo_s2.png");
        mailSender.send();
    }
}

以上发送的邮件正文为 HTML 格式,其中包括一个文字链接与图片链接,同样也包括了一个图片附件。

发送成功,以下是收到的邮件:

总结

只需稍微对 Apache Commons Email 做一个封装,便可轻易实现邮件发送功能,包括邮件附件与内嵌图片等,这些看似复杂的技术都是易如反掌。如果使用 JavaMail API 或许程序员们会写一大堆代码,才能实现这类功能,运行肯定没问题,但美观与简洁程度肯定不够。

最后我想表达的是,Apache Commons Email 也并非完美,它竟然没有对收取邮件进行一个优雅的封装,或许作者认为发送邮件的需求比较多吧,收取邮件没必要做了。

那么 Smart Mail 插件是如何简单的实现收取邮件呢?下回分解!

© 著作权归作者所有

共有 人打赏支持
黄勇

黄勇

粉丝 6303
博文 121
码字总数 216155
作品 1
浦东
CTO(技术副总裁)
加载中

评论(12)

123咔哒
123咔哒
MailConstant是哪个包的?
陈雷O

引用来自“hlevel”的评论

引用来自“hlevel”的评论

你的邮件处理失败机制呢?封装分无可厚非让 使用者更方便,更快捷,我看过很多邮件封装,还是spring 封装不错,但对于邮件失败,如 : 连接超时,邮件地址不正确,都无相应处理,其他都挺好,我自己封装了一个,,你的 邮件发送成功得返还结果,还有邮件得需要 异步多线程, 处理也总得有吧?Exception 来抓异常太笼统了吧?

引用来自“陈雷O”的评论

本人邮箱:cleisunshine@139.com
再下万分感谢!
这难为我了。。。都过3年了,不晓得当初说的啥。代码更是找不到。。

回复@hlevel : 无论怎样,还是谢谢你!
hlevel
hlevel

引用来自“hlevel”的评论

你的邮件处理失败机制呢?封装分无可厚非让 使用者更方便,更快捷,我看过很多邮件封装,还是spring 封装不错,但对于邮件失败,如 : 连接超时,邮件地址不正确,都无相应处理,其他都挺好,我自己封装了一个,,你的 邮件发送成功得返还结果,还有邮件得需要 异步多线程, 处理也总得有吧?Exception 来抓异常太笼统了吧?

引用来自“陈雷O”的评论

本人邮箱:cleisunshine@139.com
再下万分感谢!
这难为我了。。。都过3年了,不晓得当初说的啥。代码更是找不到。。
陈雷O

引用来自“hlevel”的评论

你的邮件处理失败机制呢?封装分无可厚非让 使用者更方便,更快捷,我看过很多邮件封装,还是spring 封装不错,但对于邮件失败,如 : 连接超时,邮件地址不正确,都无相应处理,其他都挺好,我自己封装了一个,,你的 邮件发送成功得返还结果,还有邮件得需要 异步多线程, 处理也总得有吧?Exception 来抓异常太笼统了吧?
本人邮箱:cleisunshine@139.com
再下万分感谢!
陈雷O

引用来自“hlevel”的评论

你的邮件处理失败机制呢?封装分无可厚非让 使用者更方便,更快捷,我看过很多邮件封装,还是spring 封装不错,但对于邮件失败,如 : 连接超时,邮件地址不正确,都无相应处理,其他都挺好,我自己封装了一个,,你的 邮件发送成功得返还结果,还有邮件得需要 异步多线程, 处理也总得有吧?Exception 来抓异常太笼统了吧?
这位仁兄,能把代码发我借鉴下吗?最近正在弄这东西。
judasn
judasn
commons-email的发送和接收这些官网引导都有说明。
但是我一直没找到,假如接收的邮箱地址是乱写、错误的,怎么判断
到底邮件有没有投递成功了?前辈是否已经找到解决方案?
我Google了很久没看到...
hlevel
hlevel

引用来自“黄勇”的评论

引用来自“hlevel”的评论

你的邮件处理失败机制呢?封装分无可厚非让 使用者更方便,更快捷,我看过很多邮件封装,还是spring 封装不错,但对于邮件失败,如 : 连接超时,邮件地址不正确,都无相应处理,其他都挺好,我自己封装了一个,,你的 邮件发送成功得返还结果,还有邮件得需要 异步多线程, 处理也总得有吧?Exception 来抓异常太笼统了吧?

嗯,您的这些建议非常好,确实得考虑这些因素,而且需要把它做得更加完备,所以我现在的标题是“初步实现”,哈哈!也希望您能提供宝贵的经验。

这个不敢当。希望smart web 越走越好。
黄勇
黄勇

引用来自“hlevel”的评论

你的邮件处理失败机制呢?封装分无可厚非让 使用者更方便,更快捷,我看过很多邮件封装,还是spring 封装不错,但对于邮件失败,如 : 连接超时,邮件地址不正确,都无相应处理,其他都挺好,我自己封装了一个,,你的 邮件发送成功得返还结果,还有邮件得需要 异步多线程, 处理也总得有吧?Exception 来抓异常太笼统了吧?

嗯,您的这些建议非常好,确实得考虑这些因素,而且需要把它做得更加完备,所以我现在的标题是“初步实现”,哈哈!也希望您能提供宝贵的经验。
hlevel
hlevel
你的邮件处理失败机制呢?封装分无可厚非让 使用者更方便,更快捷,我看过很多邮件封装,还是spring 封装不错,但对于邮件失败,如 : 连接超时,邮件地址不正确,都无相应处理,其他都挺好,我自己封装了一个,,你的 邮件发送成功得返还结果,还有邮件得需要 异步多线程, 处理也总得有吧?Exception 来抓异常太笼统了吧?
黄勇
黄勇

引用来自“scpman”的评论

你的头像好萎缩13

要的就是这个效果,哈!
初步实现 Mail 插件 —— 收取邮件

本文是《轻量级 Java Web 框架架构设计》的系列博文。 在上篇中描述了发送邮件的主要过程,今天我想和大家分享一下 Smart Mail 插件的另外一个功能 —— 收取邮件,可能没有发送邮件那么常用...

黄勇
2013/11/25
0
3
Flask 插件系列 - Flask-Mail

前往本文博客 简介 给用户发送邮件是 Web 应用中最常见的任务之一,比如用户注册,找回密码等。Python 内置了一个 smtplib 的模块,可以用来发送邮件,这里我们使用 Flask-Mail,是因为它可以...

funhacks
2017/11/29
0
0
Flask-mail扩展的基本使用

Flask-mail扩展的使用 1.安装 pip install flask-mail 2.使用 1.配置一些发送邮件的参数例如邮件发送服务器的地址,端口,是否加密等。 2.初始化flask-mail插件。 3.创建Message实例,设置发...

Jeff_Linux
2014/07/08
0
0
JFINAL邮件发送插件--jfinal-mail-plugin

jfinal-mail-plugin是jfinal的一个邮件发送插件,支持发送普通邮件、与附件邮件,邮件内容支持通过模板生成,同时还支持多个邮件发送源,她继承了Jfinal核心目标“开发迅速,代码量少,学习简...

匿名
2016/09/17
2.2K
4
Grails异步发送Mail的两种方式

方法一:使用Asynchronous mail插件,简单方便,邮件可以持久化,具体使用方法参见:异步邮件新招:Asynchronous mail插件 方法二:将Grails Mail插件与JMS、ActiveMQ合作,具体实现步骤参见...

groovyland
2010/04/17
751
0

没有更多内容

加载失败,请刷新页面

加载更多

初级开发-编程题

` public static void main(String[] args) { System.out.println(changeStrToUpperCase("user_name_abc")); System.out.println(changeStrToLowerCase(changeStrToUpperCase("user_name_abc......

小池仔
今天
6
0
现场看路演了!

HiBlock
昨天
16
0
Rabbit MQ基本概念介绍

RabbitMQ介绍 • RabbitMQ是一个消息中间件,是一个很好用的消息队列框架。 • ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。Connection是RabbitMQ的s...

寰宇01
昨天
10
0
官方精简版Windows10:微软自己都看不过去了

微软宣布,该公司正在寻求解决方案,以减轻企业客户的Windows 10规模。该公司声称,企业客户下载整个Windows 10文件以更新设备既费钱又费时。 微软宣布,该公司正在寻求解决方案,以减轻企业...

linux-tao
昨天
19
0
TypeScript基础入门之JSX(二)

转发 TypeScript基础入门之JSX(二) 属性类型检查 键入检查属性的第一步是确定元素属性类型。 内在元素和基于价值的元素之间略有不同。 对于内部元素,它是JSX.IntrinsicElements上的属性类型...

durban
昨天
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部