电子邮件
本节描述了如何使用Spring框架发送电子邮件。
Spring框架提供了一个有用的工具库,用于发送电子邮件,该库可以让你免于关注底层邮件系统的具体细节,并且代表客户端负责低级别的资源处理。
org.springframework.mail 包是 Spring Framework 邮件支持的根级包。发送邮件的核心接口是 MailSender 接口。SimpleMailMessage 类是一个简单的值对象,它封装了简单邮件的属性,如 from 和 to(以及其他许多属性)。该包还包含一系列检查型异常(checked exceptions),这些异常在较低级别的邮件系统异常基础上提供了更高级别的抽象,其中根异常为 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)
}
进一步假设我们有一个要求,即需要生成一封包含订单号的电子邮件,并将其发送给下相关订单的客户。
基本的 MailSender 和 SimpleMailMessage 使用方法
以下示例展示了如何在有人下单时使用MailSender和SimpleMailMessage来发送邮件:
- 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>
使用 JavaMailSender 和 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());
}
}
}
邮政编码是一个贯穿性的问题,很适合作为重构为自定义Spring AOP切面的候选对象,然后可以在OrderManager目标上的适当连接点(joinpoint)上运行该切面。
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("test@host.com");
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("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);
内联资源
以下示例展示了如何使用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(在上面的例子中为identifier1234)来添加到MimeMessage中的。添加文本和资源的顺序非常重要。请务必先添加文本,然后再添加资源。如果顺序相反,则无法正常工作。
使用模板库创建电子邮件内容
在前几节中展示的示例代码中,是通过使用诸如message.setText(..)这样的方法调用来显式创建电子邮件内容的。这对于简单的情况来说是可行的,在上述示例的上下文中也是可以的,因为这些示例的目的是向您展示API的最基本用法。
然而,在典型的企业应用程序中,开发人员通常不会使用前面展示的方法来创建电子邮件内容,原因有以下几点:
-
在Java代码中创建基于HTML的电子邮件内容既繁琐又容易出错。
-
显示逻辑和业务逻辑之间没有明确的区分。
-
要改变电子邮件内容的显示结构,就需要编写Java代码、重新编译、重新部署等等。
通常,解决这些问题的方法是使用模板库(如 FreeMarker)来定义电子邮件内容的显示结构。这样,你的代码只需负责创建要在电子邮件模板中渲染的数据以及发送电子邮件即可。当电子邮件内容变得稍微复杂一些时,这无疑是一种最佳实践;而借助 Spring Framework 为 FreeMarker 提供的支撑类,这一过程也变得相当容易。