33. Email

33.1 Introduction

Library dependencies

为了使用 Spring Framework 的电子邮件库,以下 JAR 必须位于应用程序的 Classpath 中。

该库可以在 Web 上免费使用,例如在 Maven Central 中为com.sun.mail:javax.mail

Spring 框架提供了一个有用的 Util 库,用于发送电子邮件,该电子邮件使用户免受底层邮件系统的限制,并负责代表 Client 端进行低级资源处理。

org.springframework.mail软件包是 Spring 框架的电子邮件支持的根级软件包。发送电子邮件的中央界面是MailSender界面; SimpleMailMessage类是封装简单邮件属性(例如* from to *(以及许多其他邮件))的简单值对象。此程序包还包含检查异常的层次结构,该检查异常的层次结构相对于较低级别的邮件系统异常(根异常为MailException)提供了更高层次的抽象。有关富邮件异常层次结构的更多信息,请参考 javadocs。

org.springframework.mail.javamail.JavaMailSender接口向MailSender接口(从中继承)提供了专用的* JavaMail *功能,例如 MIME 消息支持。 JavaMailSender还提供了用于准备名为org.springframework.mail.javamail.MimeMessagePreparator的“ MimeMessage”的回调接口。

33.2 Usage

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

public interface OrderManager {

    void placeOrder(Order order);

}

我们还假设存在一项要求,说明需要生成带有订单号的电子邮件消息并将其发送给下订单的 Client。

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());
        }
    }

}

在下面的 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的另一种方法。在这种情况下,请注意mailSender属性的类型为JavaMailSender,以便我们能够使用 JavaMail MimeMessage类:

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());
        }
    }

}

Note

邮件代码是一个横切关注点,很可能是重构为自定义 Spring AOP 方面的候选者,然后可以在OrderManager目标上的适当连接点处执行。

Spring Framework 的邮件支持随附于标准 JavaMail 实现。请参考相关的 javadocs 以获取更多信息。

33.3 使用 JavaMail MimeMessageHelper

处理 JavaMail 消息时非常方便的类是org.springframework.mail.javamail.MimeMessageHelper类,它使您不必使用冗长的 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 电子邮件允许同时使用附件和内联资源。内联资源的示例是您要在邮件中使用的图像或样式表,但不想将其显示为附件。

Attachments

下面的示例向您展示如何使用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);

Inline resources

下面的示例向您展示如何使用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);

Warning

使用指定的Content-ID(在上例中为identifier1234)将内联资源添加到MimeMessage。您添加文本和资源的 Sequences 非常重要。确保首先添加文本,然后添加资源。如果您反过来做,那就行不通了!

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

先前示例中的代码使用诸如message.setText(..)之类的方法调用显式创建了电子邮件的内容。这对于简单的情况很好,并且在上述示例的上下文中也可以,其目的是向您展示 API 的基本知识。

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

  • 用 Java 代码创建基于 HTML 的电子邮件内容很繁琐且容易出错

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

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

通常,解决这些问题的方法是使用诸如 FreeMarker 或 Velocity 之类的模板库来定义电子邮件内容的显示结构。这使您的代码只能执行创建要在电子邮件模板中呈现的数据并发送电子邮件的任务。当您的电子邮件内容变得相当复杂,并且有了 Spring Framework 对 FreeMarker 和 Velocity 的支持类变得非常容易时,这绝对是最佳实践。在下面找到一个使用 Velocity 模板库创建电子邮件内容的示例。

基于速度的示例

要使用Velocity创建电子邮件模板,您将需要在 Classpath 中提供 Velocity 库。您还需要为应用程序所需的电子邮件内容创建一个或多个 Velocity 模板。在此示例将使用的 Velocity 模板下面找到。如您所见,它是基于 HTML 的,并且由于它是纯文本,因此可以使用您喜欢的 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>

在下面找到一些简单的代码和 Spring XML 配置,该配置使用上述 Velocity 模板创建电子邮件内容并发送电子邮件。

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>