在spring框架中发送邮件

在spring框架中发送邮件

spring封装了一个邮件发送类:org.springframework.mail.javamail.JavaMailSender,这个JavaMailSender底层实现依赖的是公开的JavaMail API。所以要让spring的JavaMailSender正常工作,就必须先导入JavaMail API的实现类,比如com.sun.mail:javax.mail。

发送的邮件有两种类型,一种是简单文本邮件,不支持html,即使邮件内容中出现html标签,对方邮箱收到邮件后显示的仍是原始文本;一种是富文本邮件,支持html,对方邮箱收到邮件后会解析html标签并渲染。

一、配置JavaMailSender

PS:建议不要使用个人邮箱,我试过163的个人邮箱做发件地址,会被163拒绝发送。

 

二、新建自己的邮件发送类MailSender

 

三、使用freemarker模板发送富文本邮件

上面的MailSender.sendMimeMail( )方法发送富文本时候,需要将整个html放在字符串参数text中,这样用起来比较麻烦。因此就想有没办法像前端模板引擎那样,给MailSender一个html格式的邮件模板就能发送。折腾了一晚上,总算搞定JavaMailSender + freemarker模板的组合。

3.1 配置spring项目的freemarker configuer

因为我自己的项目用的是freemarker做模板引擎,所以在我的spring xml配置中已经配好了模板目录

如果并没有用freemarker做前端模板引擎的话,可以新建一个FreeMarkerConfigurationFactoryBean的配置

FreeMarkerConfigurer与FreeMarkerConfigurationFactoryBean其实都继承自FreeMarkerConfigurationFactory,都能生成后面药用的freemarker.template.Configuration。

3.2 修改MailSender类

然后在第二步的MailSender类中添加代码:

看起来挺简单的,但是因为路径的问题被单元测试折腾了一晚上。我还是不太理解java项目的classpath。。。=。= 到现在还是不太理解。

3.3 TemplateNotFoundException与classpath问题

先说上面代码调用freemarker模板遇到的问题吧,在进行单元测试时候,总是发生错误TemplateNotFoundException,说模板文件找不到。我用的是maven生成spring mvc项目的标准目录结构。3.1节中配置的freemarkerConfiguer路径指向”/WEB-INF/views”,所以我将mailTemplate.ftl文件也放在views目录下。

屏幕快照 2016-04-21 下午1.47.12

打开项目根目录的.classpath文件,可以看到项目开发环境的classpath其实是src/main/java与src/main/resources两个目录,项目运行时会被拷到target/classes目录。项目测试环境的classpath其实是src/test/java与src/test/resources两个目录,测试运行时会被拷到target/test-classes目录。这四个才是真正的classpath!(我觉得这个classpath是对于eclipse来说的)

屏幕快照 2016-04-21 下午1.21.07

而对于像spring mvc这样的web项目,那个src/main/webapp/WEB-INF目录又是什么呢?在spring mvc的servlet-context.xml配置为什么要放在这个路径?在servlet-context.xml中配置项的路径比如下面这些又怎么理解?

其实把web项目打成war包发布时候就知道为什么了。war包其实就是/srac/main/webapp的zip压缩包。src/main/webapp才是web项目发布时的根目录。在打包war的时候,会将原classpath下的资源文件与java代码生成的class文件拷贝到WEB-INF/classes目录下,项目依赖的jar包拷贝到WEB-INF/lib目录下。

tomcat运行web项目时会首先找到项目根目录下WEB-INF/web.xml这个配置文件进行载入。这时候,对于tomcat而言,项目的classpath应该是WEB-INF/classes与WEB-INF/lib。

而我们在web.xml,spring的xml配置文件中指定的 location =”/resources/”, templateLoaderPath=”/WEB-INF/views”,这些路径都是相对路径,相对于ApplicationContext的路径。tomcat运行的web项目,其ApplicationContext路径就是web.xml的当前路径。

下面这张表描述了spring项目中资源定位前缀的意义。

屏幕快照 2016-04-21 下午2.39.47

通过上面的描述应该稍稍能理解开发环境与发布环境的classpath的区别了,先懂了这个,再来尝试理解测试环境时为什么说找不到模板文件。

再看回上面的.classpath文件,测试环境的classpath是src/test/java与src/test/resources,目标都是放到target/test-classes。所以,要想让测试环境时能找到比如说location=”classpath:log4j.xml”、location=”classpath:config/db.properties”这样的文件,那么就应该保证该文件在src/test/resources也有一份,这样测试时才能正确读取。

而通常的项目,src/test路径下是不会包含webapp目录的,eclipse IDE也不会帮我们拷贝src/main/webapp过去。那么测试时候根本找不到stc/main/webapp目录中定义的配置与其他资源。

我用junit进行spring的单元测试时,是用file前缀指定了spring的context.xml文件的绝对路径,所以才能在测试时启动spring容器。

测试环境启动,读取到spring context.xml中配置的freemarkerConfiguer路径为/WEB-INF/views,但此时测试环境的classpath与工作目录都是target/test-classes。所以要想让测试环境能读到target/test-calsses/WEB-INF/views/mailTemplate.ftl文件,要么将该文件直接从src/main/webapp/WEB-INF/views/拷贝过来,要么可以拷贝到src/test/resources/WEB-INF/views/目录中。

为什么junit单元测试运行时路径是target/test-classes。。。 我也搞不懂 =。=# java的路径问题真是叫人伤心💔

 

四、不要在主线程中发送邮件!

发送邮件需要连接远程smtp邮件服务器,由于本地服务器到远程smtp邮件服务器之间存在网络延时,这会导致发送邮件的操作需要耗时几百毫秒甚至几秒都有可能,这取决于本地服务器到smtp服务器之间的网络状况。因此,千万不要在业务主线程中以阻塞方式发送邮件!除非你想让用户在发送邮件期间去喝一杯咖啡☕️。

正确的解决方案应该是使用多线程的方式异步发送邮件。

修改MailSender类:

PS:如果你使用junit来测试这个多线程版的sendSimpleMail方法,记得在junit主线程末尾sleep一下,否则会导致发送邮件子线程还没运行,主线程就已经结束。那子线程自然就跟着挂艹了。

又PS:由于上面这个原因,如果系统有很多像发送邮件这样需要多线程异步处理的操作,其实更好的方案应该是启动一个独立的任务处理进程,web系统将发送邮件,或者其他异步任务发给这个任务处理进程(这里可以用消息队列来实现通信),由任务进程来处理异步任务。这样就不用怕web系统重启的时候杀掉正在执行的异步任务了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注