第30章 使用Spring发送E-mail(一)

第30章 使用Spring发送E-mail

本章内容

  • 思甜前,先忆苦

  • Spring的E-mail抽象层分析

  • Spring的E-mail支持在实际开发中的应用

30.1 思甜前,先忆苦

E-mail本身的发展史可以说比互联网都要久远,其应用如此广泛,以至于我们可以在很多场合发现它的身影,这当然也包括我们日常所开发的各种应用程序。许多应用程序场景中都会用到E-mail,下面是常见的情景。

  • 用户通过浏览器或者移动设备申请了某一系统账户,系统应用程序通常会将用户最终申请结果,以E-mail的方式发送给用户进行确认。

  • 对于某些财务相关的系统来说,可能会定期地通过E-mail向系统中符合特定条件的用户发送指定的财务报表,或者出于宣传的考虑,如果用户订阅了某种服务,会定期通过E-mail的形式发送宣传资料,等等。

  • 再有就是系统监控,如果应用程序出现问题,通常情况下,可以通过E-mail的形式通知系统管理员,等等。

Java平台上有许多E-mail的解决方案,而使用范围最广,名声也是最响的,自然非Sun的JavaMail(http://java.sun.com/products/javamail/)莫属了。JavaMail虽说强大,但API的使用比较烦琐。如果不能找到一种合适的方式来管理JavaMail的API的使用,即使发送一封简单的E-mail也并非易事。

鉴于Java平台E-mail解决方案的多样性,以及JavaMail在实际开发中不慎友好的表现,Spring提出了一套E-mail抽象层来简化应用程序中发送E-mail相关的开发工作。Spring的E-mail抽象层主要对JavaMail和COS(com.oreilly.servlet包提供的E-mail解决方案)这两种E-mail解决方案提供支持。不过,COS只支持简单文本邮件的发送,而且,Spring 2.5之后就取消了对其的支持,所以,余下的内容我们将主要以JavaMail为核心展开,虽然会在合适的位置顺带提及COS的相关信息。

在正式开始介绍Spring的E-mail抽象层之前,我们有必要先回头看一下,早先要使用JavaMail API发送一封E-mail是如何做的。下方代码清单给出的代码恰好可以反映当时的情况。

String host = "your.mail.host";
String username = "..";
String passwora = "..";
String protocal = "smtp";

String mailSubject = "邮件标题";
String mailContext = "邮件内容";
String mailEncoding = "UTF-8";

boolean plainTextMail = false; // 是否发送普通文本文件

Properties props = new Properties();
props.put("mail.smtp.auth", "true");

Transport transport  = nul1;

try {
    
    
	Session session = Session.getDefaultInstance(props, null);
	MimeMessage message = new MimeMessage(session);
	message.setRecipients(RecipientType.To, new Address[]{
    
    new InternetAadress("[email protected]")});
	message.setRecipient(RecipientType.CC, new InternetAddress("[email protected]"));
	message.setFrom(new InternetAddress("[email protected]"));
	message.setSubject(mailSubject, mailEncoding);
    
	if(plainTextMail) {
    
    
		message.setText(mailContext, mailEncoding);
    }
	e1se {
    
    
		Multipart multiPart = new MimeMultipart();
		BodyPart bodyPart = new MimeBodyPart();
		bodyPart.setText(mailContext);
		multiPart.addBodyPart(bodyPart);
        
		bodyPart = new MimeBodyPart();
		FileDataSource fds = new FileDataSource("attachment.name"); // 添加附件
		bodyPart.setDataHandler(new DataHandler(fds));
		bodyPart.setFileName(fds.getName());
		multiPart.addBodyPart(bodyPart);
        
		message.setContent(multiPart);
    }
	transport = session.getTransport(protocal);
	transport.connect(host, username, password);
	transport.sendMessage(message, message.getAllRecipients());
}
catch(Exceptione) {
    
    
	// 根据情况处理异常
}
finally {
    
    
	if(transport != null) {
    
    
		try {
    
    
			transport.close();
        } catch(MessagingException e) {
    
    
			e.printStackTrace(); // 不要这样做
        }
    }
}

虽然引入相应的变量声明使得这段代码看起来更长,但是,即使忽略最初的变量声明对这段代码的长度的贡献,我们依然可以发现,直接使用JavaMail API发送一封邮件真的不是什么省心的事情。

每次使用JavaMailAPI发送邮件,我们都需要设置JavaMail的Session需要的某些属性,然后根据Session构建相应的MimeMessage实例,并在构建完的MimeMessage实例上设置将要发送的消息的方方面面。在消息准备完成后,我们才能通过Transport连接到邮件服务器,并发送准备好的邮件信息。当然,最后还不要忘了异常的处理和Transport的关闭。而实际上,在这整个过程中,每次发送Email的时候我们都要关心的,可能只是要发送什么样的邮件内容,至于如何连接到邮件服务器之类的琐事,最好让它们离我们远远的,不是吗?所以,既然我们不想每次发送邮件都写这么一堆代码,自然就得考虑对整个过程进行适度地封装,以避免每次重复编码带给我们的烦恼。

JavaMail已经推出这么长的时间了,你在使用的过程中应该早就注意到了其使用上的烦琐,也许你早就已经实现了自己对JavaMail API的封装,也许直接使用了Jakarta Commons E-mail(http://commons.apache.org/email/)对JavaMail的封装实现。这里将介绍另一种选择,那就是Spring框架提供的E-mail抽象层,它让JavaMail的使用之路不再泥泞难行。

30.2 Spring的E-mail抽象层分析

Spring所提供的Email抽象层主要体现在两个层次,即MailMessage抽象层次和MailSender抽象层次,我们可以从图30-1中获取一个初步的印象。

image-20220719154711276

MailMessage抽象层次旨在对要发送的邮件信息进行抽象和建模,MailMessage作为该层次的统领,定义了一套标准的邮件信息操作方式。如果说我们使用JavaMail API设置接收方地址之类的信息的时候,必须通过JavaMail API特定的RecipientType来指定接收方类型的话(如下):

message.setRecipient(RecipientType.CC, new InternetAddress(".."));

那么,通过MailMessage,我们直接调用语义明确的setter方法来进行同样的操作即可,如下所示:

mailMessage.setTo(..);
mailMessage.setCc(..);
mailMessage.setBcc(..);
...

这种抽象最主要的好处就是,可以让我们不需要关心最终底层使用的是什么样的E-mail解决方案,完全以一种统一的方式来准备将要发生的邮件信息就可以了。

MailMessage主要有两个实现类,SimpleMailMessage代表普通的文本邮件(plain text mail),而MimeMailMessage则代表能够发送更丰富内容的MIME(Multipart Internet Message Encoding)类型的邮件信息。MimeMailMessage是构建在JavaMail的MimeMessage上的,除了可以像SimpleMailMessage那样指定普通的文本作为要发送的邮件内容,只要是MimeMessage能够添加到邮件的内容,对于MimeMailMessage来说都不在话下。HTML形式的邮件信息,添加了附件的邮件信息,都可以由MimeMailMessage统一构建并最终被发送。而说到MailMessage的发送,我们就得提到MailSender了。

有了要发送的邮件信息之后,剩下的当然就是找个人来发送它们,而这也正是Mailsender抽象层次所要做的事情。MailSender抽象接口的主要目的就是屏蔽掉各种E-mail解决方案的差异性,不管底层的E-mail解决方案如何变换,我们只要把使用这些E-mail解决方案实现的MailSender实现类注入给依赖MailSender接口的对象即可,当前对象不需要知道具体的MailSender实现类是哪一个。MailSender的接口定义十分简单明了,如下所示:

public interface MailSender {
    
    
    void send(SimpleMailMessage simpleMessage) throws MailException;
    void send(SimpleMailMessage[] simpleMessages) throws MailException;
}

发送SimpleMailMessage的方法定义注定了直接的MailSender实现类只能发送普通的文本邮件,而COS类库恰好也只支持普通文本邮件,所以,CosMailsenderImpl就成为了第一个MailSender的直接实现类。如果我们现在要发送简单的普通文本邮件,那么现在就可以实现了,如下方代码清单所示。

// 1.准备要发送的邮件信息
SimpleMailMessage message = new SimpleMailMessage();
message.setTo("..");
message.setFrom("..");
message.setSubject("just a plain text mail");
message.setText("any thing you like");
// 2.设置MailSender并发送邮件
MailSender mai1Sender = new CosMailSenderImp1();
setUpMailSender(mailSender); // mailSender.setHost("..");
mailSender.send(message);

因为CosMailSenderImpl的功能很简单,所以,没有办法指定邮件服务器所需要的必要的验证信息,而且,使用CosMailsenderImpl发送的SimpleMailMessage也不能指定replyTo和sendDate。大部分情况下,我们不太可能会考虑在生产环境下的应用程序中使用它,这也正是Spring 2.5之后就不再提供CosMailSenderImpl的原因了吧。

MailSender只能发送普通的文本邮件,而且其唯一的直接实现类CosMailSenderImpl在这方面的表现又是如此不尽如人意,看来,我们只好寻找更好的“继承人”了。

JavaMailSender扩展自MailSender,在MailSender发送普通文本文件功能基础上,添加了发送MIME邮件的功能。大多数情况下,我们使用JavaMailSender就行了,因为使用它发送什么形式的邮件都没有问题。从刚才的图30-1上我们也能发现,JavaMailSender发送的邮件形式指向了SimpleMailMessage和MimeMailMessage两种类型。JavaMailSender唯一的一个实现类JavaMailSenderImpl使用JavaMail API实现,它完成了对JavaMail API的最终封装,可以说是最初使用JavaMail API发送邮件所经历的痛苦的终结者。至于JavaMailSenderImpl封装JavaMail API使用的方法,我不说你也应该猜出个大概,又是模板方法模式唱主角,下方代码清单给出了该实现类相应方法的代码摘录。

public void doSend(MimeMessage[] messages2Send) {
    
    
	// 预处理
	Transport transport = getTransport(getSession());
	transport.connect(getHost(), getPort(), getUsername(), getPassword());
	try {
    
    
		for(int i = 0; i < mimeMessages.length; i++) {
    
    
			MimeMessage mimeMessage = mimeMessages[i];
			transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
        }
    }
	finally {
    
    
		transport.close();
		// 后处理,比如异常的处理
    }
}

JavaMailSender提供了两种常用的邮件发送方式,一种是直接外部创建要发送的MailMesssage实例,然后传给JavaMailSender发送;另一种则是使用JavaMailSender公开给我们的MimeMessagePreparator回调接口。下面让我们看一下使用这两种方式发送邮件具体是如何操作的。

30.2.1 直接创建邮件消息并发送

在发送邮件之前,我们先设定好要用于发送邮件的JavaMailSender,如以下代码所示:

JavaMailSender mailSender = new JavaMailSenderImpl();
JavaMailSenderImpl javaMailSender = (JavaMailSenderImpl)mailSender;
javaMailSender.setHost("..");
javaMailSender.setUsername("..");
javaMailSender.setPassword("..");

Properties javaMailProps = new Properties();
javaMailProps.put("mail.smtp.auth", "true");
javaMailSender.setJavaMailProperties(javaMailProps);
// mailSender现在可以使用

当然,所有这些都可以在Spring的IoC容器中以一个bean定义搞定。代码只是出于演示的目的,实际开发中,只需要声明JavaMailSender的依赖,然后将类似下方代码清单所示的bean定义注入给相应的依赖对象即可。

<bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
	<property name="host" value=".."/>
	<property name="username" value=".."/>
	<property name="password" value=".."/>
	<property name="javaMailProperties">
		<props>
			<prop key="mail.smtp.auth">true</prop>
	    </props>
	</property>
</bean>

提示:实例化一个可用的JavaMailSenderImpl可以有两种方式,第一种就是像我们这样,直接指定本地的一些配置属性,另一种方式就是直接指定JavaMail的Session。对于那些可以获取绑定到JNDI服务的Session的应用程序来说,后一种方式就是为其量身定做的。

因为JavaMail的MimeMessage不能独立于Session而创建,所以只有在有了可用的JavaMailSender之后,我们才能开始创建要发送的邮件信息。而这一目的要通过JavaMailSender的createMimeMessage()方法来达成,如下所示:

MimeMessage javaMailMessage = mailSender.createMimeMessage();
MimeMailMessage messageWrapper = new MimeMailMessage(javaMailMessage);
messageWrapper.setTo("..");
messageWrapper.setFrom("..");
messageWrapper.setSubject("..");
messageWrapper.setText("..");
// 其他设定.....
mailSender.send(messageWrapper.getMimeMessage());

在准备要发送的邮件信息的时候,当然可以直接使用JavaMail的MimeMessage,但是,那样就不得不自已通过JavaMail的Multipart或者BodyPart来组装要发送的邮件内容。所以,我们使用了Spring提供的MimeMailMessage对createMimeMessage()返回的MimeMessage做简单的“包装”。现在,直接调用相应的setter方法设定邮件内容就可以了。在邮件准备完毕之后,最后当然就是直接调用JavaMailSender的send()方法发送,整个过程就是如此。

自己调用JavaMailSender的createMimeMessage()方法创建要发送的邮件实例当然没有什么大不了。不过,如果能够将创建邮件实例的工作从我们这里给砍掉的话,那岂不是更省事儿?这就是MimeMessagePreparator的工作。

30.2.2 使用MimeMessagePreparator发送邮件

JavaMailSender除了提供send(MimeMessage[] | MimeMessage)形式的邮件发送方法定义之外,还提供了使用MimeMessagePreparator回调接口的邮件发送方法。MimeMessagePreparator作为回调接口,可以获得JavaMailSender公开给它的MimeMessage实例。这样,我们只要在MimeMessagePreparator内一心一意地准备邮件内容就可以了,至于这个MimeMessage是怎么创建的,由谁创建的,就无需去操心了。

依然沿用已经设定完毕的JavaMailSender实例,同样的邮件发送工作现在转而使用MimeMessagePreparator实现的话,那相应的实现代码看起来应该是如下的样子:

mailSender.send(new MimeMessagePreparator() {
    
    
	public void prepare(MimeMessage message) throws Exception {
    
    
		MimeMailMessage messageWrapper = new MimeMailMessage(message);
		messageWrapper.setTo("..");
		messageWrapper.setFrom("..");
		messageWrapper.setSubject("..");
		messageWrapper.setText("..");
    }
});

实际上,MimeMailMessage提供的封装支持比较弱。正如我们所看到的那样,我们只能设定一些基本的信息,但是,要想以同样的setter方式来设定如邮件的附件等信息的话,MimeMailMessage就有些力不从心了。不过,总有后继者,MimeMessageHelper可以帮助我们完成更加丰富的邮件内容设定,下方代码清单给了MimeMessageHelper一展身手的机会。

mailSender.send(new MimeMessagePreparator() {
    
    
	public void prepare(MimeMessage message) throws Exception {
    
    
		MimeMessageHelper messageHelper = new MimeMessageHelper(message, true, "UTF-8");
		messageHelper.setFrom("..");
		messageHelper.setTo("..");
		messageHelper.setSubject("..");
		StringBuilder builder = new StringBuilder("<html><head><metahttp-equiv='ContentType' content='text/html;charset=utf-8'></head><body><pre>");
		builder.append("<img src='cid:inlinePic'>");
		builder.append("</pre></body></html>");
		messageHelper.setText(builder.toString(), true);
		messageHelper.addInline("inlinePic", new ClassPathResource("img/picture.giE"));
		messageHelper.addAttachment("fileName.pdf", new ClassPathResource("yourFile.pdf"));
    }
}

使用MimeMessageHelper,让我们再也不用去操心Multipart以及BodyPart之间的关系,以及如何组合它们。现在,我们只要根据需要,以统一的setter方式添加必要的邮件内容就行了,不亦乐乎?

注意:JavaMailSender结合MimeMessagePreparator和MimeMessageHelper的组合,是使用JavaMailSender发送邮件的最常用方式。有关MimeMessageHelper的更多细节,不妨开发过程中参阅该类的Javadoc文档,其中有详细的功能设定说明。

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125956061
今日推荐