java发送邮件的功能,针对具体的实现情况,包含有专门搭建邮件服务器和配置邮件发送信息以及模板组织等若干个步骤。整个的功能实现比较简单,不过对于怎么在工程中用好他们,还是有必要的好好总结一下。
邮件发送基础
在讨论发送具体邮件信息之前,我们先讨论一下邮件发送的一些基本概念。在很多情况下,我们发送邮件的时候,都是通过手动配置邮件客户端让它们连接到邮件服务器或者直接登录到一些网站查看邮件。实际上,他们无非是对应两种连接邮件服务器的客户端而已,一种是单机版的,一种是web版的。他们的本质还是所有发送的邮件消息提交给邮件服务器,再由邮件服务器转发到目的地。
结合我们发送邮件的步骤,我们连接到邮件服务器并发送邮件的过程无非是以下几个步骤:
1. 连接邮件服务器,身份验证。
2. 选择或者设置收件人,主题和邮件内容等。
3. 选择发送邮件,邮件服务器将内容转发。
另外一个就是,我们发送的邮件有多种协议,它们还分别对应不同的端口。比如如下是几个典型的邮件协议和对应的端口:
协议名 | 端口号 |
POP3 | 110 |
SMTP | 25 |
NNTP | 119 |
在实际的应用中,它们的端口可能会根据需要进行调整。
在我们的很多实际应用中,一般很少会需要直接配置邮件服务器,而是直接使用这些邮件服务器。尤其是对于许多免费的服务器来说,针对他们开放的端口和协议我们可以通过使用程序来实现和它们的交互,实现收发邮件。在讨论具体怎么配置邮件服务器之前,我们先讨论一下邮件发送的几种方式。
邮件发送的几种方式
我们需要发送邮件的程序一般引用了javax.mail.jar包,这个包可以在如下的链接里找到。常用的邮件发送方式主要有TLS, SSL和default这几种。他们的主要差别在于TLS和SSL都是通过加密的方式进行数据通信,而default的则是没有加密的。所以默认的方式在大多数情况下都被禁用了。
TLS
我们先看看一个TLS通信的简单示例,以gmail账户为例。假定我们有一个gmail的帐号,希望使用程序通过它发送邮件到一个指定的目的邮件地址。我们首先需要看一下gmail邮件服务器的相关设置信息:
配置项 | 具体值 |
mail.smtp.auth | true |
mail.smtp.starttls.enable | true |
mail.smtp.host | smtp.gmail.com |
mail.smtp.port | 587 |
有了前面这些配置的信息,我们可以通过它们来和gmail邮件服务器通信。首先一个前面的这些内容将作为一个配置项传入给gmail smtp server。在我们的具体实现里可以将这部分写到一个配置文件里,然后读取出来,也可以手动的设置。
手动设置的代码如下:
- Properties props = new Properties();
- props.put("mail.smtp.auth", "true");
- props.put("mail.smtp.starttls.enable", "true");
- props.put("mail.smtp.host", "smtp.gmail.com");
- props.put("mail.smtp.port", "587");
Properties props = new Properties(); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.starttls.enable", "true"); props.put("mail.smtp.host", "smtp.gmail.com"); props.put("mail.smtp.port", "587");
设置好这些属性之后下一步就是建立一个和邮件服务器连接的session了。采用这种方式需要提供用户名和密码信息。比如说我们已经有的gmail帐号为user@gmail.com, 密码为password。
那么建立session连接的代码如下:
- Session session = Session.getInstance(props,
- new javax.mail.Authenticator() {
- protected PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication(username, password);
- }
- });
Session session = Session.getInstance(props, new javax.mail.Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } });
设置好session之后的下一步就是设置我们需要发送的邮件内容等信息:
- Message message = new MimeMessage(session);
- message.setFrom(new InternetAddress(username));
- message.setRecipients(Message.RecipientType.TO,
- InternetAddress.parse("newuser@gmail.com"));
- message.setSubject("Testing Subject");
- message.setText("Dear user,"
- + " No spam to my email, please!");
- Transport.send(message);
Message message = new MimeMessage(session); message.setFrom(new InternetAddress(username)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("newuser@gmail.com")); message.setSubject("Testing Subject"); message.setText("Dear user," + " No spam to my email, please!"); Transport.send(message);
我们设定收件人的邮件地址为newuser@gmail.com,邮件的主题为Testing Subject。真正实现发送邮件的这部分代码是Transport.send(),它将最后组装成的邮件字符串发送出去。
SSL
SSL的发送方式其实和TLS大致相同,首先也是设置邮件发送的配置项,只是因为采用的协议和端口不一样,具体认证的方式也有差别。这里session的创建方式如下:
- Session session = Session.getDefaultInstance(props,
- new javax.mail.Authenticator() {
- protected PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication("abc@163.com","abc");
- }
- });
Session session = Session.getDefaultInstance(props, new javax.mail.Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("abc@163.com","abc"); } });
其他的代码基本上都是一样的,具体细节可以看附件里代码的详细实现。
除了上述的两种方式,还有一种默认的邮件发送方式,因为它不是采用的通信加密,在一些本地局域网的测试环境里可能会用到。和前面的代码里主要的区别就在于它的创建session的方式很简单,一般不需要做任何身份验证,典型的代码实现如下:
- Session session = Session.getDefaultInstance(props);
Session session = Session.getDefaultInstance(props);
这里的props就是环境的基本配置项,典型的如下:
- mail.smtp.auth=false
- mail.smtp.starttls.enable=false
- mail.smtp.port=25
- mail.smtp.host=localhost
- username=
- password=
mail.smtp.auth=false mail.smtp.starttls.enable=false mail.smtp.port=25 mail.smtp.host=localhost username= password=
环境安装和配置
这里主要用到java邮件服务器james。其实我们可以使用的邮件服务器很多,比如postfix, sendmail。主要是james的安装配置比较简单。它的配置主要就以下几个步骤:
1. 安装jdk和jre
2. 安装james server并配置默认server名字等信息。
安装james server比较简单,直接到官网下载整个的zip包到本地,然后解压就可以了。
解压到本地之后,到本地目录的/james-2.3.2/apps/james/SAR-INF下面,打开config.xml文件。在这里配置邮箱的域名和服务器的名字。
- <config>
- <James>
- <postmaster>support@abc.com</postmaster>
- <servernames autodetect="true" autodetectIP="true">
- <servername>abc.com</servername>
- </servernames>
- <James>
- <config>
<config> <James> <postmaster>support@abc.com</postmaster> <servernames autodetect="true" autodetectIP="true"> <servername>abc.com</servername> </servernames> <James> <config>
3. 配置环境变量JAVA_HOME
将java安装环境的变量配置好,这个部分不同的系统和平台不一样,都可以参考网上的流程。
4. 启动James server
在linux环境下启动james server的时候需要注意,因为考虑到发送邮件访问的权限。这里需要切换到具有管理员权限的帐号下然后再启动它们。
启动的命令如下:./bin/run.sh
启动后的界面如下图:
邮件内容组织
我们从前面的代码内容里可以看到,javamail里发送的最后都是字符串。可是在我们实际发送的邮件里,可以包含有文件各种格式定义,甚至很多html的元素,这是怎么做到的呢?实际上这些是和做web应用的思想很类似。我们常用的web框架强调的是各种关注点分离,所以才有了MVC的结构思想。在邮件内容的输出里,我们也可以实现一种分离。我们将邮件需要发送的内容和内容组织的形式进行分离。这样,我们只需要在代码里将要显示的内容发送给模板,而在模板的组织里具体实现怎么来显示这些内容。
同时,为了实现对我们定义的这种类型格式的支持,我们需要在发送的email message里设置一下:
- message.setContent(content, "text/html")
message.setContent(content, "text/html")
我们常用的一个实现这种内容和组织形式的分离的工具就是velocity。我们可以在如下链接中下载它。
我们实现模板解析的代码如下:
- public String prepareMessage(MessagePayload payload, String templatePath) {
- if(payload == null || StringUtil.isNullOrEmpty(templatePath)) {
- throw new IllegalArgumentException("Invalid payload or template path.");
- }
- VelocityEngine engine = new VelocityEngine();
- engine.init();
- VelocityContext context = new VelocityContext();
- context.put("payload", payload);
- Template t = engine.getTemplate(templatePath);
- StringWriter writer = new StringWriter();
- t.merge( context, writer );
- return writer.toString();
- }
public String prepareMessage(MessagePayload payload, String templatePath) { if(payload == null || StringUtil.isNullOrEmpty(templatePath)) { throw new IllegalArgumentException("Invalid payload or template path."); } VelocityEngine engine = new VelocityEngine(); engine.init(); VelocityContext context = new VelocityContext(); context.put("payload", payload); Template t = engine.getTemplate(templatePath); StringWriter writer = new StringWriter(); t.merge( context, writer ); return writer.toString(); }
大家可以看到,在这部分代码里,无非就是将我们传入的MessagePayload对象揉合到模板中,并返回最终的字符串。那么这些我们传入的对象是怎么在模板里被处理的呢?我们来看看模板里的具体内容:
- <html>
- <head>
- <title>Order Candy Service</title>
- </head>
- <body>
- <p>User $payload.getUserId() has successfully requested to order the stuff:
- #set($mpcInstance = $payload.getInstancePayload())
- #if(!$payload.isOrderForSelf())
- for user: $payload.getCustName()
- #end
- with the following information:<p>
- Account Name: $payload.getAcctName() <br>
- Instance Name: $mpcInstance.getName() <br>
- #set($candyNumber = $mpcInstance.getCandies().size() / 10.0)
- Number of Candies: $candyNumber <br>
- <p>Best regards.<br>
- Candy Shop Team<br>
- </body>
- </html>
<html> <head> <title>Order Candy Service</title> </head> <body> <p>User $payload.getUserId() has successfully requested to order the stuff: #set($mpcInstance = $payload.getInstancePayload()) #if(!$payload.isOrderForSelf()) for user: $payload.getCustName() #end with the following information:<p> Account Name: $payload.getAcctName() <br> Instance Name: $mpcInstance.getName() <br> #set($candyNumber = $mpcInstance.getCandies().size() / 10.0) Number of Candies: $candyNumber <br> <p>Best regards.<br> Candy Shop Team<br> </body> </html>
除了那些常用的html元素,我们看到了一些类似于常用编程语言的元素,比如#if, #set等。这些都是velocity里支持的模板语言。所以如果我们传入的是一个个的java对象,它也能够解析,就像我们使用普通的java对象一样。这样关于怎么判断和组织内容就通过这个模板语言解决了。如果使用过其他编程语言的web框架的,比如python之类的,对于这些东西应该就不会陌生了。
总结
一个发送邮件的功能一般包含有配置邮件发送的协议,端口,邮件的发件人,收件人以及邮件内容等。如果我们希望能够很好的组织起来每个部分,需要针对每个不同的需求选择最合适的工具,这样对于长期的管理和维护来说也省了很多事。
参考材料
http://james.apache.org/server/
http://www.ibm.com/developerworks/library/j-james1/
http://velocity.apache.org/