第30章 使用Spring发送E-mail

30.3 Spring的E-mail支持在实际开发中的应用

实际开发中,我们不可能像实例那样,直接通过字符串的拼接来创建所要发送的邮件的具体内容。更多时候,我们会使用系统指定的邮件模板。当发送给用户的邮件内容需要变更的时候(比如公司迁址,需要变更新的地址或者电话之类细节),我们不想挨个类地去改代码,而使用邮件模板的话,只要修改一下邮件模板就可以了。

从现在开始,我将向你展示如何在Spring中使用其E-mail支持来发送基于模板的电子邮件。虽然主要以Velocity作为使用的模板技术,但使用其他模板技术(比如FreeMarker)在道理上是相同的,只可能会存在少许具体操作上的差异。

在开始着手正式的工作之前,我们先构建一个要发送的邮件模板,使用Velocity的VTL编写模板如下:

来到魔法屋,就得听我的指示,我来选出符合条件的人,你来决定他们的命运!
嘿,轮到你了!${player}

以上邮件模板内容可以vm模板文件形式保存,也可以存入数据库中。这里先暂且将其存入magicHouse.vm文件,并将该文件放在Classpath的根路径下。为了能够重用使用邮件模板发送邮件这一功能,我们对该功能进行抽象,抽象后的接口如下方代码清单所示:

public interface ITemplateMailAgent {/*** convenient sending method with only recipient.TO <br/>*/void sendMail(String receiver, String subject, String templateKey, Map<String, Object> context);/*** central method to send E-mail with template support.<br/>* @param receivers Recipients as a Parameter wrapper object.* @param templateKey the identity of the template to use.* @param context contains data that will be merged into template.*/void sendMail(Recipients receivers, String subject, String templateKey, Map<String, Object> context);
}

当然,该抽象接口定义并不完善。如果要实际应用的话,或许要添加更多的方法来提供更为全面的支持,甚至,如果你觉得这种方法签名定义不爽,那就改成自己喜欢的风格。不管怎么样,我们将先就这一定义开始基于邮件模板的邮件发送整合之路。

ITemplateMailAgent的抽象允许我们提供基于不同模板技术的实现类,我们使用Velocity,所以,对应Velocity的ITemplateMailAgent实现类就此诞生了:

public class VelocityTemplateMailAgent implements ITemplateMailAgent {private final String DEFAULT_SENDER = "sender@XXX.com.cn";private final String DEFAULT_ENCODING = "UTF~8";private String mailFrom = DEFAULT_SENDER;private String mai1Encoding = DEFAULT_ENCODING;private JavaMailSender javaMailSender;private VelocityEngine velocityEngine;public void sendMail(String receiver, String subject, String templateKey, Map<String, Object> context) {Recipients recipients = new Recipients(receiver);sendMail(recipients, subject, templateKey, context);}public void sendMail(finalR?cipients receivers, final String subject, String templateKey, Map<String, Object> context) {validateRecipients(receivers);Validate.notEmpty(templateKey);StringWriter writer =new StringWriter();VelocityEngineUtils.mergeTemplate(velocityEngine, templateKey, getMailEncoding(), context, writer);final String mailText = writer.toString(); // mail content is readygetJavaMailSender().send(new MimeMessagePreparator() {public void prepare(MimeMessage message) throws Exception {MimeMessageHelper helper = new MimeMessageHelper(message, getMailEncoding());helper.setFrom(getMailFrom());helper.setTo(receivers.getTo());if(!CollectionUtils.isEmpty(receivers.getCcList())) {helper.setCc(receivers.getCc());}if(!CollectionUtils.isEmpty(receivers.getBccList())) {helper.setBcc(receivers.getBcc());helper.setSubject(subject);helper.setText(mailText);}}});}private void validateRecipients(Recipients receivers) {// ...}// 用于依赖注入的gettcr和setter方法定义
}

VelocityTemplateMailAgent的实现逻辑依赖于两个主要组件,这可以推而广之到使用其他模板技术的ITemplateMailAgent实现类中,如下所述。

  • 模板引擎——VelocityEngine。我们需要使用具体的模板引擎来合并具体的邮件模板和要发送的数据,合并后的结果自然就成为我们将要最终发送的邮件内容。

  • JavaMailSender。没有JavaMailSender的支持,我们好像也发送不了邮件吧?(当然,使用其他Email封装方案则另当别论。)

至于其他的实现细节,我想从代码上来看,并不是太难理解,所以,这里就不做太多解释了。

注意:VelocityTemplateMailAgent的当前实现只支持基于邮件模板的普通文本邮件的发送。如果要支持MIME邮件,那需要做更多的工作,你不妨自己尝试一下。

现在只要将velocityTemplateMailAgent和它的相关依赖添加到Spring的IoC容器,或者编程组装在一起,我们就能发送基于邮件模板的邮件了。当前,我们不妨像通常那样,通过Spring的IoC容器来做这部分工作,如下方代码清单所示。

<bean id="cpLoaderVelocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean"><property name="configLocation" value="classpath:cn/spring21/conf/velocity-config.properties"/>
</bean><bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"><property name="host" value=".."/><property name="username" value=".."/><property name="password" value=".."/><property name="javaMailProperties"><props><prop key="mail.smtp.auth">true</prop></props>
</property>
</bean><bean id="templatEmailAgent" class="org.darrenstudio.books.unveilspring.mail.template.VelocityTemplateMailAgent"><property name="javaMailSender" ref="javaMailSender"></property><property name="velocityEngine" ref="cpLoaderVelocityEngine"/>
</bean>

VelocityTemplateMailAgent所依赖的VelocityEngine是我们通过Spring提供的VelocityEngineFactoryBean注入给它的。VelocityEngineFactoryBean作为一个FactoryBean实现,封装了一个VelocityEngine相关的设定,使我们可以简单bean的形式将VelocityEngine添加到Spring的IoC容器进行管理。在Spring MVC部分,我们也应该接触过该类了。VelocityEngineFactoryBean使用的配置文件内容比较简单,如下所示,当然,如果需要,你可以在使用的时候追加更多控制项:

# velocity-config.properties
resource.loader = classpath
classpath.resource.loader.description = Classpath Resource Loader
classpath.resource.loader.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceloadervelocimacro.library =

我们的velocity-config.properties配置,使得VelocityEngine将从Classpath加载vm模板文件。所以,现在我们可以通过如下的形式发送最初模板所设定的邮件内容了:

velocityTemplateMailAgentmailAgent = ... // 通过IoC容器注入或查找相应实例
Map<String, Object> context = new HashMap<String,Object>();
context.put("player", "孙小美");
mailAgent.sendMail("fujohnwang@gmail.com", "邮件标题", "magicHouse.vm", context);

像收件人、邮件标题,以及合并到邮件模板的数据,通常需要我们根据应用程序的上下文来获得。不管怎么样,如果你想看一下效果的话,那图30-2是我的Gmail所呈现的。

看起来已经达到预期的效果了!

注意:有关结合Velocity模板和Spring发送邮件的内容,最新的Spring文档中都有介绍。不过,如果我没有记错的话,最早提出这种实践思路的是一篇文章“Sending Velocity-Based E-Mail With Spring”。如果你感兴趣的话,不妨根据本书后面的参考资料找这篇文章读读。

将模板文件放入应用程序的Classpath或者直接放到文件系统中,有时候会因为应用程序场景的变化产生些许不便。比如,多个应用程序都需要访问同一模板内容的话,这些应用程序可能需要将同一份模板分别打包,或者通过其他途径,来访问文件系统中存放的同一份模板文件。所以,出于这些场景的考虑,我们的模板文件也会被放入某种类型的数据存储服务中,比如数据库或者目录服务器上。这种情况下,我们就需要在使用VelocityTemplateMailAgent之前,做点儿附加工作,以便让VelocityEngine可以加载位于特定数据存储服务中的邮件模板。

假设我们将magicHouse.vm以及系统中其他邮件模板的内容存入数据库。为了让Velocity能够从数据库加载这些模板资源,我们可以替换Velocity最初使用的ResourceLoader实现,从ClasspathResourceLoader转而使用DataSourceResourceLoader。整个切换过程涉及的,实际上也只是velocity-config.properties配置内容的少许改变。应该说,这种思路是正确的。不过,实施当中我们可能会碰到点儿小问题!

通常,使用DataSourceResourceLoader的配置文件内容可能是如下方代码清单所示的样子。

resource.loader = ds
ds.resource.loader.public.name = DataSource
?s.resource.loader.description = VelocityDataSourceResourceLoader
ds.resource.loader.class = org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader
ds.resource.loader.resource.datasource = java:comp/env/jdbc/Velocity
ds.resource.loader.resource.table = mail_templates
ds.resource.loader.resource.keycolumn = TEMPLATEKEY
ds.resource.loader.resource.templatecolumn = TEMPLATE_DEFINIATION
ds.resource.loader.resource.timestampcolumn = UPDATE_DATE
ds.resource.loader.cache = false
ds.resource.loader.modificationCheckInterval = 60

从中可以看出,DataSourceResourceLoader只能使用从JNDI获取的DataSource来加载模板资源。如果要使用外部独立的数据源,那么需要做一些类似如下的编码工作:

DataSourceResourceLoader ds = new DataSourceResourceLoader();
DataSourcedataSource = ...; // 可以从其他地方注入
ds.setDataSource(dataSource);
velocityEngine.setProperty("ds.resource.loader.instance", ds);

如果Spring的VelocityEngineFactoryBean提供了setResourceLoader(..)的方法的话(实际上该名称的方法确实提供了,但却是接受Spring的ResourceLoader类型作为方法参数,而不是Velocity的ResourceLoader类型),我们可以通过一个FactoryBean来封装以上代码所示的对DataSourceResourceLoader进行设定的相关逻辑,但不巧,事情没有按照我们所预想的方向发展,我们得另寻他路。

VelocityEngineFactoryBean定义有一个postProcessVelocityEngine(VelocityEngine)方法。该方法为protected,可以允许子类覆写它,以对VelocityEngineFactoryBean所管理的velocityEngine实例做进一步的定制。所以,我们可以从这里着手,解决DataSourceResourceLoader使用外部独立数据源的问题。既然VelocityEngineFactoryBean没有提供我们所需要的设置选项,那我们就扩展它。扩展后的VelocityEngineFactoryBean如下方代码清单所示。

public class ExtendedVelocityEngineFactoryBean extends VelocityEngineFactoryBean {private DataSource dataSource;@Overrideprotected void postProcessVelocityEngine(VelocityEngine velocityEngine) throws IOException,VelocityException {super.postProcessVelocityEngine(velocityEngine);DataSourceResourceLoader resourceLoader = new DataSourceResourceLoader();resourceLoader.setDataSource(dataSource);velocityEngine.setProperty("ds.resource.loader.instance", resourceLoader);}public DataSource getDataSource() {return dataSource;}public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}
}

现在,以ExtendedVelocityEngineFactoryBean替换掉最初的VelocityEngineFactoryBean,并注入给VelocityTemplateMailAgent,我们的VelocityTemplateMailAgent就可以从数据库来加载邮件模板了。

不过好事多磨,在此之前,需要注意余下的几个问题。

(1)启用了使用外部数据源的DataSourceResourceLoader之后,velocity-config.properties中有关内容需要注释掉(或者直接删掉),否则,Velocity依然会沿用配置文件中的配置内容。注释后的配置文件如下方代码清单所示。

ds.resource.loader.public.name = DataSource
ds.resource.loader.description = VelocityDataSourceResourceLoader
# ds.resource.loader.class = org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader
# ds.resource.loader.resource.datasource = java:comp/env/jdbc/Veloci.ty
ds.resource.loader.resource.table = mail_templates
ds.resource.loader.resource.keycolumn = TEMPLATE_KEY
ds.resource.loader.resource.templatecolumn = TEMPLATE_DEFINIATION
ds.resource.loader.resource.timestampcolumn = UPDATE_DATE

(2)说是从数据库加载模板,我们还没有将模板内容导入数据库,所以,根据配置内容所示,我们需要建立一个名为mail_templates的表,然后声明模板对应的标志列和模板的定义内容列等,表定义DDL如下所示:

CREATE TABLE mail_templates (TEMPLATE_KEY varchar(25) NOT NULL,TEMPLATE_DEFINIATION text NOT NULL,UPDATEDATE datetime NOT NULL,CREATEDDATE datetime NOT NULL,PRIMARYKEY(TEMPLATE_KEY)
)

至于实际系统中,要以什么作为表名,以什么列唯一标志模板,以及以什么列来保存模板内容,那是你自己的事情了,无非就是更改一下velocity-config.properties的配置内容而已。

假如我们通过如下SQL插入模板内容的话:

insert into mail_templates values('rich4', '来到魔法屋,就得听我的指示,我来选出符合条件的人,你来决定他们的命运!\n嘿,轮到你了!${player}', NOW(), NOW())

那使用VelocityTemplateMailAgent进行邮件发送的如下代码,可以获得与从Classpath或者文件系统加载邮件模板同样的效果:

VelocityTermplateMailAgent mailAgent = // 通过IoC容器注入或者查找相应实例;
Map<String, Object> context = new HashMap<String, Object>();
context.put("player", 孙小美");
mailAgent.sendMail("fujohnwang@gmail.com", "邮件标题", "rich4", context);

其实只是模板文件名换成了数据库表中的标志键。

提示:以上几种场景都是基于Velocity进行介绍的,如果你愿意使用其他模板技术,可以在此基础上进行适度的调整,也可以达到同样的目的。实际上,不使用Velocity的DataSourceResourceLoader,我们同样可以达到从数据库获取邮件模板的目的,而且,还要更加灵活,适用范围也更广,能猜到是什么吗?(想想Spring的数据访问相关内容。)

30.4 小结

与JavaEE平台上许多API的境遇类似,JavaMailAPI虽强大,但依然没能摆脱实际应用中的烦琐。本章我们先忆苦,简单回顾了早些时候JavaMailAPI的实践之路,在此基础上,我们引入Spring的Mail抽象层相关内容。我们一起了解了Spring的Mail抽象层的方方面面,同时也对Spring的Mail抽象层在实际开发用的应用场景进行了简单的探索。希望你结束本章的阅读之后,能够借助Spring的Mail抽象层的支持,“多快好省”地完成日常开发中各种Mai相关功能。

第30章 使用Spring发送E-mail(二)相关推荐

  1. Spring - Java/J2EE Application Framework 应用框架 第 17 章 使用Spring邮件抽象层发送Email

    第 17 章 使用Spring邮件抽象层发送Email 17.1. 简介 Spring提供了一个发送电子邮件的高级抽象层,它向用户屏蔽了底层邮件系统的一些细节,同时负责低层次的代表客户端的资源处理. ...

  2. 第7章 使用Spring MVC构建Web程序(一)

    7.1 Spring MVC起步 7.1.1 跟踪Spring MVC的请求 在spring MVC中,DispatcherServlet是前端控制器,客户端的请求以及各种请求(处理器映射器,处理器适 ...

  3. B2C电子商务网站使用Spring发送激活账号的电子邮件

     电子商务网站使用 Spring 发送激活账号的电子邮件 一.    前面的准备工作 1:邮箱服务器的设置:    我使用的是QQ邮箱服务器来实现的,下面的操作就以QQ邮箱服务器为例.如果你的QQ邮箱 ...

  4. 使用Spring发送带附件的电子邮件(站内和站外传送)

    1.使用Spring发送带附件的电子邮件 <?xml version="1.0" encoding="UTF-8"?> <beansxmlns ...

  5. stm32h7 串口idle_【STM32H7教程】第30章 STM32H7的USART应用之八个串口FIFO实现

    第30章       STM32H7的USART应用之八个串口FIFO实现 本章节为大家讲解STM327的8个串口的FIFO驱动实现,后面的ESP8266,GPS,RS485,GPRS等试验都是建立在 ...

  6. 第22章 迈向Spring MVC的旅程

    第22章 迈向Spring MVC的旅程 本章内容 Servlet独行天下的时代 繁盛一时的JSP时代 Servlet与JSP的联盟 数英雄人物,还看今朝 "子曰:温故而知新",如 ...

  7. 【RL-TCPnet网络教程】第30章 RL-TCPnet之SNTP网络时间获取

    第30章      RL-TCPnet之SNTP网络时间获取 本章节为大家讲解RL-TCPnet的SNTP应用,学习本章节前,务必要优先学习第29章的NTP基础知识.有了这些基础知识之后,再搞本章节会 ...

  8. Spring实战 | 第二部分 Web中的Spring(第五章 构建Spring Web应用程序)

    第五章 构建Spring Web应用程序 映射请求到Spring控制器 透明地绑定表单参数 校验表单提交 一.Spring MVC起步 1.跟踪spring MVC的请求 在请求离开浏览器时,会带有用 ...

  9. 【第3版emWin教程】第30章 emWin6.x的SIF格式全字库生成和使用方法(Unicode编码,QSPI Flash方案)

    教程不断更新中:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429 第30章       emWin6.x的SIF格式全字库生成和使 ...

最新文章

  1. mysql的索引类型以及优缺点
  2. Hemberg-lab单细胞转录组数据分析(二)
  3. 控制反转 php,[PHP学习] 控制反转以及依赖注入的日常使用
  4. 哪几所大学计算机软件方面是强项,计算机软件工程专业排名靠前的大学是那几所...
  5. tkinter Canvas画图片大坑总结
  6. 如今前端程序员还有前途吗?
  7. Carbon Copy Cloner for Mac(磁盘克隆/同步/备份工具)直装版
  8. Numbers 档案如何转Excel .xlsx 格式?
  9. 2020-08-17每日一句
  10. 基于asp.net719圆通快递物流管理系统
  11. 这几个小众软件能支持英文文档翻译成中文
  12. 大道至简——软件工程实践者的思想知识导图
  13. mysql中身份证号判断男女人数
  14. 功能测试的类型之用户验收测试
  15. python爬虫找漫画出现断章?那你就得看这篇爬虫思想教学咯!
  16. gitlab漏洞导致服务器被植入挖矿程序
  17. NTC热敏电阻检测温度
  18. vue 框架总结 使用elementUI
  19. JS学习日记--面向对象
  20. 空中飞行汽车,人类的未来交通?

热门文章

  1. 机房空调制冷量计算方法
  2. 《人再囧途之泰囧》之黄渤
  3. 教师资格证材料分析题答题模板,可直接套用!
  4. android图片透明度跟缩放大小动画事件
  5. UOS上扫描仪驱动笔记
  6. 《管理学》领导、控制、创新的摘要笔记
  7. 自定义右键菜单 html,自定义右键菜单代码详解(转)
  8. ERP系统总体解决方案 附下载地址
  9. jQuery 参考手册
  10. 科技云报道:酷栈科技xView,不只是比肩传统PC的全能体验