Email

本节描述如何使用 Spring Framework 发送电子邮件。 .Library dependencies 为了使用 Spring Framework 的电子邮件支持,以下 JAR 需要在应用程序的类路径中:

此库可在网上免费获得 - 例如,在 Maven Central 中作为 com.sun.mail:jakarta.mail。请务必使用最新的 2.x 版本(该版本使用 jakarta.mail 包名称空间),而不是 Jakarta Mail 1.6.x(使用 javax.mail 包名称空间)。 Spring Framework 提供了一个有用的实用程序库,用于发送电子邮件,该库可屏蔽底层邮件系统的具体信息,并负责代表客户端进行低级别资源处理。 org.springframework.mail 包是 Spring Framework 电子邮件支持的根级别包。用于发送电子邮件的中心接口是 MailSender`接口。一个封装简单邮件属性的简单值对象(例如 `fromto(还有许多其他属性))是 SimpleMailMessage 类。该包还包含了一系列经过检查的异常,这些异常在较底层的邮件系统异常之上提供了更高级别的抽象,而根异常是 MailException. 有关丰富的邮件异常层次结构的更多信息,请参见 javadoc. org.springframework.mail.javamail.JavaMailSender 接口为 MailSender 接口(它从中继承)添加了专门的 JavaMail 功能,例如 MIME 消息支持。JavaMailSender 还提供了一个名为 org.springframework.mail.javamail.MimeMessagePreparator 的回调接口,用于准备 MimeMessage

Usage

假设我们有一个称为 OrderManager 的业务接口,如下例所示:

  • Java

  • Kotlin

public interface OrderManager {

	void placeOrder(Order order);
}
interface OrderManager {

	fun placeOrder(order: Order)
}

进一步假设我们有一个要求,规定需要生成一封包含订单号的电子邮件并将其发送给下达相关订单的客户。

Basic MailSender and SimpleMailMessage Usage

下面的示例展示了如何使用 MailSenderSimpleMailMessage 在有人下单时发送电子邮件:

  • Java

  • Kotlin

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

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

}
class SimpleOrderManager : OrderManager {

	lateinit var mailSender: MailSender
	lateinit var templateMessage: SimpleMailMessage

	override fun 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

		val msg = SimpleMailMessage(this.templateMessage)
		msg.setTo(order.customer.emailAddress)
		msg.text = ("Dear " + order.customer.firstName
				+ order.customer.lastName
				+ ", thank you for placing order. Your order number is "
				+ order.orderNumber)
		try {
			mailSender.send(msg)
		} catch (ex: MailException) {
			// simply log it and go on...
			System.err.println(ex.message)
		}
	}
}

下面的示例展示了上述代码的 bean 定义:

  • Java

  • Kotlin

  • Xml

@Bean
JavaMailSender mailSender() {
	JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
	mailSender.setHost("mail.mycompany.example");
	return mailSender;
}

@Bean // this is a template message that we can pre-load with default state
SimpleMailMessage templateMessage() {
	SimpleMailMessage message = new SimpleMailMessage();
	message.setFrom("customerservice@mycompany.example");
	message.setSubject("Your order");
	return message;
}

@Bean
SimpleOrderManager orderManager(JavaMailSender mailSender, SimpleMailMessage templateMessage) {
	SimpleOrderManager orderManager = new SimpleOrderManager();
	orderManager.setMailSender(mailSender);
	orderManager.setTemplateMessage(templateMessage);
	return orderManager;
}
@Bean
fun mailSender(): JavaMailSender {
	return JavaMailSenderImpl().apply {
		host = "mail.mycompany.example"
	}
}

@Bean // this is a template message that we can pre-load with default state
fun templateMessage() = SimpleMailMessage().apply {
	from = "customerservice@mycompany.example"
	subject = "Your order"
}


@Bean
fun orderManager(javaMailSender: JavaMailSender, simpleTemplateMessage: SimpleMailMessage) = SimpleOrderManager().apply {
	mailSender = javaMailSender
	templateMessage = simpleTemplateMessage
}
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
	<property name="host" value="mail.mycompany.example"/>
</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="customerservice@mycompany.example"/>
	<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>

Using JavaMailSender and MimeMessagePreparator

本部分描述了使用 MimeMessagePreparator 回调接口的 OrderManager 的另一种实现。在以下示例中,mailSender 属性的类型是 JavaMailSender,以便我们可以使用 JavaMail MimeMessage 类:

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

import jakarta.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("mail@mycompany.example"));
				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());
		}
	}

}

邮件代码是一种跨功能考虑,很可能是重构为 custom Spring AOP aspect 的候选代码,然后可以在 OrderManager 目标的适当连接点上运行。

Spring Framework 的邮箱支持附带标准 JavaMail 实现。有关更多信息,请参阅相关的 JavaDoc。

Using the 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("test@host.com");
helper.setText("Thank you for ordering!");

sender.send(message);

Sending Attachments and Inline Resources

多部分电子邮件消息允许附件和内联资源。内联资源示例包括您想在邮件中使用的图片或样式表,但又不想作为附件显示。

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("test@host.com");

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("test@host.com");

// 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 将内联资源添加到 MimeMessage(在上面的示例中为 identifier1234)。添加文本和资源的顺序非常重要。确保首先添加文本,然后添加资源。如果您反过来做,它将不起作用。

Creating Email Content by Using a Templating Library

上一部分展示的示例中的代码使用诸如 message.setText(..) 这样的方法调用显式创建电子邮件消息内容。对于简单的情况来说,这种方法不错,并且这些示例的目的是向您展示 API 的基本知识,因此这样做并无问题。

然而,对于典型的企业应用程序,由于以下原因,开发者通常不会使用上述方法创建电子邮件消息内容:

  • 在 Java 代码中创建基于 HTML 的电子邮件内容既乏味又容易出错。

  • 没有清晰地将显示逻辑与业务逻辑分开。

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

通常,解决这些问题的做法是使用一个模板库(例如 FreeMarker)来定义电子邮件内容的显示结构。这样,你的代码的任务就只是创建要在电子邮件模板中呈现的数据并发送电子邮件。当电子邮件内容变得稍微复杂时,这种方法绝对是最佳实践,有了 Spring Framework 对 FreeMarker 的支持类,做到这点很容易。