电子邮件

本节介绍如何使用 Spring 框架发送电子邮件。

库依赖项

为了使用 Spring 框架的电子邮件支持,您的应用程序的类路径中需要包含以下 JAR 文件

此库可在网络上免费获取,例如,在 Maven Central 中作为com.sun.mail:jakarta.mail。请确保使用最新的 2.x 版本(使用jakarta.mail包命名空间),而不是 Jakarta Mail 1.6.x(使用javax.mail包命名空间)。

Spring 框架提供了一个有用的实用程序库用于发送电子邮件,该库可以保护您免受底层邮件系统的具体细节的影响,并负责代表客户端处理低级资源。

org.springframework.mail包是 Spring 框架电子邮件支持的根级包。发送电子邮件的核心接口是MailSender接口。一个简单的值对象,封装了简单邮件的属性,例如fromto(以及许多其他属性),是SimpleMailMessage类。此包还包含一个已检查异常的层次结构,该层次结构提供了对较低级别邮件系统异常的更高级别抽象,根异常是MailException。有关丰富的邮件异常层次结构的更多信息,请参阅javadoc

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

用法

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

  • Java

  • Kotlin

public interface OrderManager {

	void placeOrder(Order order);
}
interface OrderManager {

	fun placeOrder(order: Order)
}

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

基本MailSenderSimpleMailMessage用法

以下示例演示了如何使用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("[email protected]");
	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 = "[email protected]"
	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="[email protected]"/>
	<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>

使用JavaMailSenderMimeMessagePreparator

本节介绍OrderManager的另一种实现,该实现使用MimeMessagePreparator回调接口。在以下示例中,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("[email protected]"));
				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());
		}
	}

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

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

使用 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("[email protected]");
helper.setText("Thank you for ordering!");

sender.send(message);

发送附件和内联资源

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

附件

以下示例演示了如何使用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("[email protected]");

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

内联资源

以下示例演示了如何使用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("[email protected]");

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

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

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

但是,在您的典型企业应用程序中,开发人员通常不会使用前面显示的方法来创建电子邮件内容,原因有很多

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

  • 显示逻辑和业务逻辑之间没有明确的分离。

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

通常,解决这些问题的方法是使用模板库(如 FreeMarker)来定义电子邮件内容的显示结构。这使得您的代码只需创建要在电子邮件模板中呈现的数据并发送电子邮件即可。当电子邮件内容变得稍微复杂时,这绝对是一种最佳实践,并且借助 Spring 框架对 FreeMarker 的支持类,它变得非常容易实现。