33. 电子邮件

33.1 简介


库依赖

以下 JAR 需要位于 order 中的_applass 的 classpath,以使用 Spring Framework 的电子邮件 library。

这个 library 可以在 web 上免费获取 - 对于 example,在 Maven Central 中可以作为com.sun.mail:javax.mail


Spring Framework 提供了一个有用的实用程序 library,用于发送电子邮件,保护用户免受底层邮件系统的细节影响,并负责代表 client 进行低 level 资源处理。

org.springframework.mail包是 Spring Framework 电子邮件支持的 root level 包。发送电子邮件的中央界面是MailSender界面;一个简单的 value object 封装了一个简单邮件的 properties,例如 from 和 to(以及许多其他邮件)是SimpleMailMessage class。此程序包还包含已检查的 exceptions 层次结构,这些 exceptions 在较低的 level 邮件系统 exceptions 上提供了更高级别的抽象,其中根 exception 为MailException。有关富邮件 exception 层次结构的更多信息,请参阅 javadocs。

org.springframework.mail.javamail.JavaMailSender接口向MailSender接口(从中继承)添加了专门的 JavaMail features,例如 MIME 消息支持。 JavaMailSender还提供了一个回调接口,用于准备一个名为org.springframework.mail.javamail.MimeMessagePreparator的'MimeMessage'。

33.2 用法

我们假设有一个名为OrderManager的业务接口:

public interface OrderManager {

    void placeOrder(Order order);

}

我们还假设需要声明需要生成带有 order 编号的电子邮件消息并将其发送给放置相关 order 的客户。

33.2.1 基本 MailSender 和 SimpleMailMessage 用法

import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;

public class SimpleOrderManager implements OrderManager {

    private MailSender mailSender;
    private SimpleMailMessage templateMessage;

    public void setMailSender(MailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void setTemplateMessage(SimpleMailMessage templateMessage) {
        this.templateMessage = templateMessage;
    }

    public void placeOrder(Order order) {

        // Do the business calculations...

        // Call the collaborators to persist the order...

        // Create a thread safe "copy" of the template message and customize it
        SimpleMailMessage msg = new SimpleMailMessage(this.templateMessage);
        msg.setTo(order.getCustomer().getEmailAddress());
        msg.setText(
            "Dear " + order.getCustomer().getFirstName()
                + order.getCustomer().getLastName()
                + ", thank you for placing order. Your order number is "
                + order.getOrderNumber());
        try{
            this.mailSender.send(msg);
        }
        catch (MailException ex) {
            // simply log it and go on...
            System.err.println(ex.getMessage());
        }
    }

}

在下面查找上面 code 的 bean 定义:

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="mail.mycompany.com"/>
</bean>

<!-- this is a template message that we can pre-load with default state -->
<bean id="templateMessage" class="org.springframework.mail.SimpleMailMessage">
    <property name="from" value="[emailprotected]"/>
    <property name="subject" value="Your order"/>
</bean>

<bean id="orderManager" class="com.mycompany.businessapp.support.SimpleOrderManager">
    <property name="mailSender" ref="mailSender"/>
    <property name="templateMessage" ref="templateMessage"/>
</bean>

33.2.2 使用 JavaMailSender 和 MimeMessagePreparator

这是使用MimeMessagePreparator回调接口的OrderManager的另一个_implement。请注意,在这种情况下mailSender property 的类型为JavaMailSender,以便我们能够使用 JavaMail MimeMessage class:

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import javax.mail.internet.MimeMessage;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessagePreparator;

public class SimpleOrderManager implements OrderManager {

    private JavaMailSender mailSender;

    public void setMailSender(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void placeOrder(final Order order) {
        // Do the business calculations...
        // Call the collaborators to persist the order...

        MimeMessagePreparator preparator = new MimeMessagePreparator() {
            public void prepare(MimeMessage mimeMessage) throws Exception {
                mimeMessage.setRecipient(Message.RecipientType.TO,
                        new InternetAddress(order.getCustomer().getEmailAddress()));
                mimeMessage.setFrom(new InternetAddress("[emailprotected]"));
                mimeMessage.setText("Dear " + order.getCustomer().getFirstName() + " " +
                        order.getCustomer().getLastName() + ", thanks for your order. " +
                        "Your order number is " + order.getOrderNumber() + ".");
            }
        };

        try {
            this.mailSender.send(preparator);
        }
        catch (MailException ex) {
            // simply log it and go on...
            System.err.println(ex.getMessage());
        }
    }

}

mail code 是一个横切关注的问题,很可能是重构为自定义 Spring AOP aspect的候选者,然后可以在OrderManager目标上的适当连接点执行。

Spring Framework 的邮件支持附带标准的 JavaMail implementation。有关更多信息,请参阅相关的 javadoc。

33.3 使用 JavaMail MimeMessageHelper

处理 JavaMail 消息时非常方便的 class 是org.springframework.mail.javamail.MimeMessageHelper class,它使您不必使用详细的 JavaMail API。使用MimeMessageHelper可以很容易地创建一个MimeMessage

// of course you would use DI in any real-world cases
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");

MimeMessage message = sender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message);
helper.setTo("[emailprotected]");
helper.setText("Thank you for ordering!");

sender.send(message);

33.3.1 发送附件和内联资源

Multipart 电子邮件允许附件和内联资源。内联资源的示例可能是您要在邮件中使用的图像或样式表,但您不希望将其显示为附件。

附件

以下 example 显示如何使用MimeMessageHelper发送电子邮件以及单个 JPEG 图像附件。

JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");

MimeMessage message = sender.createMimeMessage();

// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[emailprotected]");

helper.setText("Check out this image!");

// let's attach the infamous windows Sample file (this time copied to c:/)
FileSystemResource file = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addAttachment("CoolImage.jpg", file);

sender.send(message);

内联资源

以下 example 显示如何使用MimeMessageHelper发送电子邮件和内嵌图像。

JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost("mail.host.com");

MimeMessage message = sender.createMimeMessage();

// use the true flag to indicate you need a multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setTo("[emailprotected]");

// use the true flag to indicate the text included is HTML
helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);

// let's include the infamous windows Sample file (this time copied to c:/)
FileSystemResource res = new FileSystemResource(new File("c:/Sample.jpg"));
helper.addInline("identifier1234", res);

sender.send(message);

使用指定的Content-ID(上面的 example 中的identifier1234)将内联资源添加到MimeMessage。添加文本和资源的 order 非常重要。一定要先添加文本,然后再添加资源。如果你这样做,反过来,它将无法正常工作!

33.3.2 使用模板 library 创建电子邮件内容

前面示例中的 code 使用方法 calls(例如message.setText(..))显式创建了电子邮件消息的内容。这对于简单的情况很好,并且在上述示例的 context 中是可以的,其目的是向您展示 API 的基础知识。

但是,在典型的企业应用程序中,出于多种原因,您不会使用上述方法创建电子邮件的内容。

  • 在 Java code 中创建 HTML-based 电子邮件内容是单调乏味且容易出错

  • 显示逻辑和业务逻辑之间没有明确的区别

  • 更改电子邮件内容的显示结构需要编写 Java code,重新编译,重新部署...

通常,解决这些问题的方法是使用模板 library(如 FreeMarker 或 Velocity)来定义电子邮件内容的显示结构。这使得 code 的任务只是创建要在电子邮件模板中呈现的数据并发送电子邮件。当电子邮件的内容变得相当复杂时,这绝对是一种最佳实践,并且使用 Spring Framework 的支持 Class for FreeMarker 和 Velocity 变得非常容易。在下面找到使用 Velocity 模板 library 创建电子邮件内容的示例。

A Velocity-based example

要使用速度创建电子邮件 template(s),您需要在 classpath 上提供 Velocity libraries。您还需要为 application 所需的电子邮件内容创建一个或多个 Velocity 模板。在下面找到此 example 将使用的 Velocity 模板。正如您所看到的那样它是 HTML-based,并且因为它是纯文本,所以可以使用您喜欢的 HTML 或文本编辑器创建它。

# in the com/foo/package
<html>
    <body>
        <h3>Hi ${user.userName}, welcome to the Chipping Sodbury On-the-Hill message boards!</h3>

        <div>
            Your email address is <a href="mailto:${user.emailAddress}">${user.emailAddress}</a>.
        </div>
    </body>
</html>

在下面找到一些简单的 code 和 Spring XML configuration,它们利用上面的 Velocity 模板来创建电子邮件内容并发送 email(s)。

package com.foo;

import org.apache.velocity.app.VelocityEngine;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.ui.velocity.VelocityEngineUtils;

import javax.mail.internet.MimeMessage;
import java.util.HashMap;
import java.util.Map;

public class SimpleRegistrationService implements RegistrationService {

    private JavaMailSender mailSender;
    private VelocityEngine velocityEngine;

    public void setMailSender(JavaMailSender mailSender) {
        this.mailSender = mailSender;
    }

    public void setVelocityEngine(VelocityEngine velocityEngine) {
        this.velocityEngine = velocityEngine;
    }

    public void register(User user) {

        // Do the registration logic...

        sendConfirmationEmail(user);
    }

    private void sendConfirmationEmail(final User user) {
        MimeMessagePreparator preparator = new MimeMessagePreparator() {
            public void prepare(MimeMessage mimeMessage) throws Exception {
                MimeMessageHelper message = new MimeMessageHelper(mimeMessage);
                message.setTo(user.getEmailAddress());
                message.setFrom("[emailprotected]"); // could be parameterized...
                Map model = new HashMap();
                model.put("user", user);
                String text = VelocityEngineUtils.mergeTemplateIntoString(
                        velocityEngine, "com/dns/registration-confirmation.vm", model);
                message.setText(text, true);
            }
        };
        this.mailSender.send(preparator);
    }

}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <property name="host" value="mail.csonth.gov.uk"/>
    </bean>

    <bean id="registrationService" class="com.foo.SimpleRegistrationService">
        <property name="mailSender" ref="mailSender"/>
        <property name="velocityEngine" ref="velocityEngine"/>
    </bean>

    <bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
        <property name="velocityProperties">
            <value>
                resource.loader=class
                class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
            </value>
        </property>
    </bean>

</beans>