摘要:
JavaMail API是读取、撰写、发送电子信息的可选包。我们可用它来建立如Eudora、Foxmail、MS Outlook Express一般的邮件用户代理程序(Mail User Agent,简称MUA)。让我们看看JavaMail API是如何提供信息访问功能的吧!JavaMail API被设计用于以不依赖协议的方式去发送和接收电子信息,文中着重:如何以不依赖于协议的方式发送接收电子信息,这也是本文所要描述的.一、JavaMail API简介
JavaMail API是读取、撰写、发送电子信息的可选包。我们可用它来建立如Eudora、Foxmail、MS Outlook Express一般的邮件用户代理程序(Mail User Agent,简称MUA)。而不是像sendmail或者其它的邮件传输代理(Mail Transfer Agent,简称MTA)程序那样可以传送、递送、转发邮件。从另外一个角度来看,我们这些电子邮件用户日常用MUA程序来读写邮件,而MUA依赖着 MTA处理邮件的递送。
在清楚了到MUA与MTA之间的关系后,让我们看看JavaMail API是如何提供信息访问功能的吧!JavaMail API被设计用于以不依赖协议的方式去发送和接收电子信息,这个API被分为两大部分:
基本功能:如何以不依赖于协议的方式发送接收电子信息,这也是本文所要描述的,不过在下文中,大家将看到这只是一厢情愿而已。
第二个部分则是依赖特定协议的,比如SMTP、POP、IMAP、NNTP协议。在这部分的JavaMail API是为了和服务器通讯,并不在本文的内容中。
二、相关协议一览
在我们步入JavaMail API之前,先看一下API所涉及的协议。以下便是大家日常所知、所乐于使用的4大信息传输协议:
SMTP
POP
IMAP
MIME
当 然,上面的4个协议,并不是全部,还有NNTP和其它一些协议可用于传输信息,但是由于不常用到,所以本文便不提及了。理解这4个基本的协议有助于我们更 好的使用JavaMail API。然而JavaMail API是被设计为与协议无关的,目前我们并不能克服这些协议的束缚。确切的说,如果我们使用的功能并不被我们选择的协议支持,那么JavaMail API并不可能如魔术师一样神奇的赋予我们这种能力。
1.SMTP
简 单邮件传输协议定义了递送邮件的机制。在下文中,我们将使用基于Java-Mail的程序与公司或者ISP的SMTP服务器进行通讯。这个SMTP服务器 将邮件转发到接收者的SMTP服务器,直至最后被接收者通过POP或者IMAP协议获取。这并不需要SMTP服务器使用支持授权的邮件转发,但是却的确要 注意SMTP服务器的正确设置(SMTP服务器的设置与JavaMail API无关)。
2.POP
POP 是一种邮局协议,目前为第3个版本,即众所周知的POP3。POP定义了一种用户如何获得邮件的机制。它规定了每个用户使用一个单独的邮箱。大多数人在使 用POP时所熟悉的功能并非都被支持,例如查看邮箱中的新邮件数量。而这个功能是微软的Outlook内建的,那么就说明微软Outlook之类的邮件客 户端软件是通过查询最近收到的邮件来计算新邮件的数量来实现前面所说的功能。因此在我们使用JavaMail API时需要注意,当需要获得如前面所讲的新邮件数量之类的信息时,我们不得不自己进行计算。
3.IMAP
IMAP 使用在接收信息的高级协议,目前版本为第4版,所以也被称为IMAP4。需要注意的是在使用IMAP时,邮件服务器必须支持该协议。从这个方面讲,我们并 不能完全使用IMAP来替代POP,不能期待IMAP在任何地方都被支持。假如邮件服务器支持IMAP,那么我们的邮件程序将能够具有以下被IMAP所支 持的特性:每个用户在服务器上可具有多个目录,这些目录能在多个用户之间共享。
其与POP相比高级之处显而易见,但是在尝试采取IMAP时,我们 认识到它并不是十分完美的:由于IMAP需要从其它服务器上接收新信息,将这些信息递送给用户,维护每个用户的多个目录,这都为邮件服务器带来了高负载。 并且IMAP与POP的一个不同之处是POP用户在接收邮件时将从邮件服务器上下载邮件,而IMAP允许用户直接访问邮件目录,所以在邮件服务器进行备份 作业时,由于每个长期使用此邮件系统的用户所用的邮件目录会占有很大的空间,这将直接导致邮件服务器上磁盘空间暴涨。
4.MIME
MIME 并不是用于传送邮件的协议,它作为多用途邮件的扩展定义了邮件内容的格式:信息格式、附件格式等等。一些RFC标准都涉及了MIME:RFC 822, RFC 2045, RFC 2046, RFC 2047,有兴趣的Matrixer可以阅读一下。而作为JavaMail API的开发者,我们并不需关心这些格式定义,但是这些格式被用在了程序中。
5.NNTP和其它的第三方协议
正因为JavaMail API在设计时考虑到与第三方协议实现提供商之间的分离,故我们可以很容易的添加一些第三方协议。SUN维护着一个第三方协议实现提供商的列表: http://java.sun.com/products/javamail/Third_Party.html,通过此列表我们可以找到所需要的而又不被SUN提供支持的第三方协议:比如NNTP这个新闻组协议和S/MIME这个安全的MIME协议。
三、安装
1.安装JavaMail
为了使用JavaMail API,需要从 http://java.sun.com/products/javamail/downloads/index.html下载文件名格式为javamail-[version].zip的文件(这个文件中包括了JavaMail实现),并将其中的mail.jar文件添加到CLASSPATH中。这个实现提供了对SMTP、IMAP4、POP3的支持。
注意:在安装JavaMail实现之后,我们将在demo目录中发现许多有趣的简单实例程序。
在 安装了JavaMail之后,我们还需要安装JavaBeans Activation Framework,因为这个框架是JavaMail API所需要的。如果我们使用J2EE的话,那么我们并无需单独下载JavaMail,因为它存在于J2EE.jar中,只需将J2EE.jar加入到 CLASSPATH即可。
2.安装JavaBeans Activation Framework
从 http://java.sun.com/products/javabeans/glasgow/jaf.html下 载JavaBeans Activation Framework,并将其添加到CLASSPATH中。此框架增加了对任何数据块的分类、以及对它们的处理的特性。这些特性是JavaMail API需要的。虽然听起来这些特性非常模糊,但是它对于我们的JavaMail API来说只是提供了基本的MIME类型支持。
到此为止,我们应当把mail.jar和activation.jar都添加到了CLASSPATH中。
当然如果从方便的角度讲,直接把这两个Jar文件复制到JRE目录的lib/ext目录中也可以。
四、初次认识JavaMail API
1.了解我们的JavaMail环境
A.纵览JavaMail核心类结构
打 开JavaMail.jar文件,我们将发现在javax.mail的包下面存在着一些核心类:Session、Message、Address、 Authenticator、Transport、Store、Folder。而且在javax.mail.internet包中还有一些常用的子类。
B.Session
Session类定义了基本的邮件会话。就像Http会话那样,我们进行收发邮件的工作都是基于这个会话的。Session对象利用了java.util.Properties对象获得了邮件服务器、用户名、密码信息和整个应用程序都要使用到的共享信息。
Session类的构造方法是私有的,所以我们可以使用Session类提供的getDefaultInstance()这个静态工厂方法获得一个默认的Session对象:
Properties props = new Properties();
// fill props with any information
Session session = Session.getDefaultInstance(props, null);
或者使用getInstance()这个静态工厂方法获得自定义的Session:
Properties props = new Properties();
// fill props with any information
Session session = Session.getInstance(props, null);
从上面的两个例子中不难发现,getDefaultInstance()和getInstance()方法的第二个参数都是null,这是因为在上面的例子中并没有使用到邮件授权,下文中将对授权进行详细介绍。
从很多的实例看,在对mail server进行访问的过程中使用共享的Session是足够的,即使是工作在多个用户邮箱的模式下也不例外。
C.Message
当 我们建立了Session对象后,便可以被发送的构造信息体了。在这里SUN提供了Message类型来帮助开发者完成这项工作。由于Message是一 个抽象类,大多数情况下,我们使用javax.mail.internet.MimeMessage这个子类,该类是使用MIME类型、MIME信息头的 邮箱信息。信息头只能使用US-ASCII字符,而非ASCII字符将通过编码转换为ASCII的方式使用。
为了建立一个MimeMessage对象,我们必须将Session对象作为MimeMessage构造方法的参数传入:
MimeMessage message = new MimeMessage(session);
注意:对于MimeMessage类来讲存在着多种构造方法,比如使用输入流作为参数的构造方法。
在建立了MimeMessage对象后,我们需要设置它的各个part,对于MimeMessage类来说,这些part就是MimePart接口。最基本的设置信息内容的方法就是通过表示信息内容和米么类型的参数调用setContent()方法:
message.setContent("Hello", "text/plain");
然而,如果我们所使用的MimeMessage中信息内容是文本的话,我们便可以直接使用setText()方法来方便的设置文本内容。
message.setText("Hello");
前面所讲的两种方法,对于文本信息,后者更为合适。而对于其它的一些信息类型,比如HTML信息,则要使用前者。
别忘记了,使用setSubject()方法对邮件设置邮件主题:
message.setSubject("First");
D.Address
到这里,我们已经建立了Session和Message,下面将介绍如何使用邮件地址类:Address。像Message一样,Address类也是一个抽象类,所以我们将使用javax.mail.internet.InternetAddress这个子类。
通过传入代表邮件地址的字符串,我们可以建立一个邮件地址类:
Address address = new InternetAddress("president@whitehouse.gov");
如果要在邮件地址后面增加名字的话,可以通过传递两个参数:代表邮件地址和名字的字符串来建立一个具有邮件地址和名字的邮件地址类:
Address address = new InternetAddress("president@whitehouse.gov", "George Bush");
本文在这里所讲的邮件地址类是为了设置邮件信息的发信人和收信人而准备的,在建立了邮件地址类后,我们通过message的setFrom()和setReplyTo()两种方法设置邮件的发信人:
message.setFrom(address);
message.setReplyTo(address);
若在邮件中存在多个发信人地址,我们可用addForm()方法增加发信人:
Address address[] = ...;
message.addFrom(address);
为了设置收信人,我们使用addRecipient()方法增加收信人,此方法需要使用Message.RecipientType的常量来区分收信人的类型:
message.addRecipient(type, address)
下面是Message.RecipientType的三个常量:
Message.RecipientType.TO
Message.RecipientType.CC
Message.RecipientType.BCC
因此,如果我们要发送邮件给总统,并发用一个副本给第一夫人的话,下面的方法将被用到:
Address toAddress = new InternetAddress("vice.president@whitehouse.gov");
Address ccAddress = new InternetAddress("first.lady@whitehouse.gov");
message.addRecipient(Message.RecipientType.TO, toAddress);
message.addRecipient(Message.RecipientType.CC, ccAddress);
JavaMail API并没有提供检查邮件地址有效性的机制。当然我们可以自己完成这个功能:验证邮件地址的字符是否按照RFC822规定的格式书写或者通过DNS服务器上的MX记录验证等。
E.Authenticator
像java.net 类那样,JavaMail API通过使用授权者类(Authenticator)以用户名、密码的方式访问那些受到保护的资源,在这里“资源”就是指邮件服务器。在 javax.mail包中可以找到这个JavaMail的授权者类(Authenticator)。
在使用Authenticator这个抽象类 时,我们必须采用继承该抽象类的方式,并且该继承类必须具有返回PasswordAuthentication对象(用于存储认证时要用到的用户名、密 码)getPasswordAuthentication()方法。并且要在Session中进行注册,使Session能够了解在认证时该使用哪个类。
下面代码片断中的MyAuthenticator就是一个Authenticator的子类。
Properties props = new Properties();
// fill props with any information
Authenticator auth = new MyAuthenticator();
Session session = Session.getDefaultInstance(props, auth);
F.Transport
在发送信息时,Transport类将被用到。这个类实现了发送信息的协议(通称为SMTP),此类是一个抽象类,我们可以使用这个类的静态方法send()来发送消息:
Transport.send(message);
当然,方法是多样的。我们也可由Session获得相应协议对应的Transport实例。并通过传递用户名、密码、邮件服务器主机名等参数建立与邮件服务器的连接,并使用sendMessage()方法将信息发送,最后关闭连接:
message.saveChanges(); // implicit with send()
Transport transport = session.getTransport("smtp");
transport.connect(host, username, password);
transport.sendMessage(message, message.getAllRecipients());
transport.close();
评 论:上面的方法是一个很好的方法,尤其是在我们在同一个邮件服务器上发送多个邮件时。因为这时我们将在连接邮件服务器后连续发送邮件,然后再关闭掉连接。 send()这个基本的方法是在每次调用时进行与邮件服务器的连接的,对于在同一个邮件服务器上发送多个邮件来讲可谓低效的方式。
注意:如果需要在发送邮件过程中监控mail命令的话,可以在发送前设置debug标志:
session.setDebug(true)。
G.Store和Folder
接 收邮件和发送邮件很类似都要用到Session。但是在获得Session后,我们需要从Session中获取特定类型的Store,然后连接到 Store,这里的Store代表了存储邮件的邮件服务器。在连接Store的过程中,极有可能需要用到用户名、密码或者Authenticator。
// Store store = session.getStore("imap");
Store store = session.getStore("pop3");
store.connect(host, username, password);
在连接到Store后,一个Folder对象即目录对象将通过Store的getFolder()方法被返回,我们可从这个Folder中读取邮件信息:
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
Message message[] = folder.getMessages();
上 面的例子首先从Store中获得INBOX这个Folder(对于POP3协议只有一个名为INBOX的Folder有效),然后以只读 (Folder.READ_ONLY)的方式打开Folder,最后调用Folder的getMessages()方法得到目录中所有Message的数 组。
注意:对于POP3协议只有一个名为INBOX的Folder有效,而对于IMAP协议,我们可以访问多个Folder(想想前面讲 的IMAP协议)。而且SUN在设计Folder的getMessages()方法时采取了很智能的方式:首先接收新邮件列表,然后再需要的时候(比如读 取邮件内容)才从邮件服务器读取邮件内容。
在读取邮件时,我们可以用Message类的getContent()方法接收邮件或是writeTo()方法将邮件保存,getContent()方法只接收邮件内容(不包含邮件头),而writeTo()方法将包括邮件头。
System.out.println(((MimeMessage)message).getContent());
在读取邮件内容后,别忘记了关闭Folder和Store。
folder.close(aBoolean);
store.close();
传递给Folder.close()方法的boolean 类型参数表示是否在删除操作邮件后更新Folder。
H.继续向前进!
在讲解了以上的七个Java Mail核心类定义和理解了简单的代码片断后,下文将详细讲解怎样使用这些类实现JavaMail API所要完成的高级功能。
五、使用JavaMail API
在明确了JavaMail API的核心部分如何工作后,本人将带领大家学习一些使用Java Mail API任务案例。
1.发送邮件
在获得了Session后,建立并填入邮件信息,然后发送它到邮件服务器。这便是使用Java Mail API发送邮件的过程,在发送邮件之前,我们需要设置SMTP服务器:通过设置Properties的mail.smtp.host属性。
String host = ...;
String from = ...;
String to = ...;
// Get system properties
Properties props = System.getProperties();
// Setup mail server
props.put("mail.smtp.host", host);
// Get session
Session session = Session.getDefaultInstance(props, null);
// Define message
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
message.setSubject("Hello JavaMail");
message.setText("Welcome to JavaMail");
// Send message
Transport.send(message);
由于建立邮件信息和发送邮件的过程中可能会抛出异常,所以我们需要将上面的代码放入到try-catch结构块中。
2.接收邮件
为了在读取邮件,我们获得了session,并且连接到了邮箱的相应store,打开相应的Folder,然后得到我们想要的邮件,当然别忘记了在结束时关闭连接。
String host = ...;
String username = ...;
String password = ...;
// Create empty properties
Properties props = new Properties();
// Get session
Session session = Session.getDefaultInstance(props, null);
// Get the store
Store store = session.getStore("pop3");
store.connect(host, username, password);
// Get folder
Folder folder = store.getFolder("INBOX");
folder.open(Folder.READ_ONLY);
// Get directory
Message message[] = folder.getMessages();
for (int i=0, n=message.length; i<n; i++) {
System.out.println(i + ": " + message[i].getFrom()[0]
+ "/t" + message[i].getSubject());
}
// Close connection
folder.close(false);
store.close();
上面的代码所作的是从邮箱中读取每个邮件,并且显示邮件的发信人地址和主题。从技术角度讲,这里存在着一个异常的可能:当发信人地址为空时,getFrom()[0]将抛出异常。
下 面的代码片断有效的说明了如何读取邮件内容,在显示每个邮件发信人和主题后,将出现用户提示从而得到用户是否读取该邮件的确认,如果输入YES的话,我们 可用Message.writeTo(java.io.OutputStream os)方法将邮件内容输出到控制台上,关于Message.writeTo()的具体用法请看JavaMail API。
BufferedReader reader = new BufferedReader (
new InputStreamReader(System.in));
// Get directory
Message message[] = folder.getMessages();
for (int i=0, n=message.length; i<n; i++) {
System.out.println(i + ": " + message[i].getFrom()[0]
+ "/t" + message[i].getSubject());
System.out.println("Do you want to read message? " +
"[YES to read/QUIT to end]");
String line = reader.readLine();
if ("YES".equals(line)) {
message[i].writeTo(System.out);
} else if ("QUIT".equals(line)) {
break;
}
}
3.删除邮件和标志
设置与message相关的Flags是删除邮件的常用方法。这些Flags表示了一些系统定义和用户定义的不同状态。在Flags类的内部类Flag中预定义了一些标志:
Flags.Flag.ANSWERED
Flags.Flag.DELETED
Flags.Flag.DRAFT
Flags.Flag.FLAGGED
Flags.Flag.RECENT
Flags.Flag.SEEN
Flags.Flag.USER
但 需要在使用时注意的:标志存在并非意味着这个标志被所有的邮件服务器所支持。例如,对于删除邮件的操作,POP协议不支持上面的任何一个。所以要确定哪些 标志是被支持的——通过访问一个已经打开的Folder对象的getPermanetFlags()方法,它将返回当前被支持的Flags类对象。
删除邮件时,我们可以设置邮件的DELETED标志:
message.setFlag(Flags.Flag.DELETED, true);
但是首先要采用READ_WRITE的方式打开Folder:
folder.open(Folder.READ_WRITE);
在对邮件进行删除操作后关闭Folder时,需要传递一个true作为对删除邮件的擦除确认。
folder.close(true);
Folder类中另一种用于删除邮件的方法expunge()也同样可删除邮件,但是它并不为sun提供的POP3实现支持,而其它第三方提供的POP3实现支持或者并不支持这种方法。
另外,介绍一种检查某个标志是否被设置的方法:Message.isSet(Flags.Flag flag)方法,其中参数为被检查的标志。
4.邮件认证
我 们在前面已经学会了如何使用Authenticator类来代替直接使用用户名和密码这两字符串作为 Session.getDefaultInstance()或者Session.getInstance()方法的参数。在前面的小试牛刀后,现在我们将 了解到全面认识一下邮件认证。
我们在此取代了直接使用邮件服务器主机名、用户名、密码这三个字符串作为连接到POP3 Store的方式,使用存储了邮件服务器主机名信息的属性文件,并在获得Session时传入自定义的Authenticator实例:
// Setup properties
Properties props = System.getProperties();
props.put("mail.pop3.host", host);
// Setup authentication, get session
Authenticator auth = new PopupAuthenticator();
Session session = Session.getDefaultInstance(props, auth);
// Get the store
Store store = session.getStore("pop3");
store.connect();
PopupAuthenticator 类继承了抽象类Authenticator,并且通过重载Authenticator类的getPasswordAuthentication()方法返 回PasswordAuthentication类对象。而getPasswordAuthentication()方法的参数param是以逗号分割的 用户名、密码组成的字符串。
import javax.mail.*;
import java.util.*;
public class PopupAuthenticator extends Authenticator {
public PasswordAuthentication getPasswordAuthentication(String param) {
String username, password;
StringTokenizer st = new StringTokenizer(param, ",");
username = st.nextToken();
password = st.nextToken();
return new PasswordAuthentication(username, password);
}
}
5.回复邮件
回 复邮件的方法很简单:使用Message类的reply()方法,通过配置回复邮件的收件人地址和主题(如果没有提供主题的话,系统将默认将“Re:”作 为邮件的主体),这里不需要设置任何的邮件内容,只要复制发信人或者reply-to到新的收件人。而reply()方法中的boolean参数表示是否 将邮件回复给发送者(参数值为false),或是恢复给所有人(参数值为true)。
补充一下,reply-to地址需要在发信时使用setReplyTo()方法设置。
MimeMessage reply = (MimeMessage)message.reply(false);
reply.setFrom(new InternetAddress("president@whitehouse.gov"));
reply.setText("Thanks");
Transport.send(reply);
6.转发邮件
转发邮件的过程不如前面的回复邮件那样简单,它将建立一个转发邮件,这并非一个方法就能做到。
每 个邮件是由多个部分组成,每个部分称为一个邮件体部分,是一个BodyPart类对象,对于MIME类型邮件来讲就是MimeBodyPart类对象。这 些邮件体包含在成为Multipart的容器中对于MIME类型邮件来讲就是MimeMultiPart类对象。在转发邮件时,我们建立一个文字邮件体部 分和一个被转发的文字邮件体部分,然后将这两个邮件体放到一个Multipart中。说明一下,复制一个邮件内容到另一个邮件的方法是仅复制它的 DataHandler(数据处理者)即可。这是由JavaBeans Activation Framework定义的一个类,它提供了对邮件内容的操作命令的访问、管理了邮件内容操作,是不同的数据源和数据格式之间的一致性接口。
// Create the message to forward
Message forward = new MimeMessage(session);
// Fill in header
forward.setSubject("Fwd: " + message.getSubject());
forward.setFrom(new InternetAddress(from));
forward.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
// Create your new message part
BodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setText(
"Here you go with the original message:/n/n");
// Create a multi-part to combine the parts
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);
// Create and fill part for the forwarded content
messageBodyPart = new MimeBodyPart();
messageBodyPart.setDataHandler(message.getDataHandler());
// Add part to multi part
multipart.addBodyPart(messageBodyPart);
// Associate multi-part with message
forward.setContent(multipart);
// Send message
Transport.send(forward);
7.使用附件
附件作为与邮件相关的资源经常以文本、表格、图片等格式出现,如流行的邮件客户端一样,我们可以用JavaMail API从邮件中获取附件或是发送带有附件的邮件。
A.发送带有附件的邮件
发送带有附件的邮件的过程有些类似转发邮件,我们需要建立一个完整邮件的各个邮件体部分,在第一个部分(即我们的邮件内容文字)后,增加一个具有DataHandler的附件而不是在转发邮件时那样复制第一个部分的DataHandler。
如果我们将文件作为附件发送,那么要建立FileDataSource类型的对象作为附件数据源;如果从URL读取数据作为附件发送,那么将要建立URLDataSource类型的对象作为附件数据源。
然后将这个数据源(FileDataSource或是URLDataSource)对象作为DataHandler类构造方法的参数传入,从而建立一个DataHandler对象作为数据源的DataHandler。
接着将这个DataHandler设置为邮件体部分的DataHandler。这样就完成了邮件体与附件之间的关联工作,下面的工作就是BodyPart的setFileName()方法设置附件名为原文件名。
最后将两个邮件体放入到Multipart中,设置邮件内容为这个容器Multipart,发送邮件。
// Define message
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
message.setSubject("Hello JavaMail Attachment");
// Create the message part
BodyPart messageBodyPart = new MimeBodyPart();
// Fill the message
messageBodyPart.setText("Pardon Ideas");
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(messageBodyPart);
// Part two is attachment
messageBodyPart = new MimeBodyPart();
DataSource source = new FileDataSource(filename);
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(filename);
multipart.addBodyPart(messageBodyPart);
// Put parts in message
message.setContent(multipart);
// Send the message
Transport.send(message);
如果我们使用servlet实现发送带有附件的邮件,则必须上传附件给servlet,这时需要注意提交页面form中对编码类型的设置应为multipart/form-data。
<FORM ENCTYPE="multipart/form-data"
method=post action="/myservlet">
<INPUT TYPE="file" NAME="thefile">
<INPUT TYPE="submit" VALUE="Upload">
</FORM>
B.读取邮件中的附件
读取邮件中的附件的过程要比发送它的过程复杂一点。因为带有附件的邮件是多部分组成的,我们必须处理每一个部分获得邮件的内容和附件。
但 是如何辨别邮件信息内容和附件呢?Sun在Part类(BodyPart类实现的接口类)中提供了getDisposition()方法让开发者获得邮件 体部分的部署类型,当该部分是附件时,其返回之将是Part.ATTACHMENT。但附件也可以没有部署类型的方式存在或者部署类型为 Part.INLINE,无论部署类型为Part.ATTACHMENT还是Part.INLINE,我们都能把该邮件体部分导出保存。
Multipart mp = (Multipart)message.getContent();
for (int i=0, n=multipart.getCount(); i<n; i++) {
Part part = multipart.getBodyPart(i));
String disposition = part.getDisposition();
if ((disposition != null) &&
((disposition.equals(Part.ATTACHMENT) ||
(disposition.equals(Part.INLINE))) {
saveFile(part.getFileName(), part.getInputStream());
}
}
下列代码中使用了saveFile方法是自定义的方法,它根据附件的文件名建立一个文件,如果本地磁盘上存在名为附件的文件,那么将在文件名后增加数字表示区别。然后从邮件体中读取数据写入到本地文件中(代码省略)。
// from saveFile()
File file = new File(filename);
for (int i=0; file.exists(); i++) {
file = new File(filename+i);
}
以上是邮件体部分被正确设置的简单例子,如果邮件体部分的部署类型为null,那么我们通过获得邮件体部分的MIME类型来判断其类型作相应的处理,代码结构框架如下:
if (disposition == null) {
// Check if plain
MimeBodyPart mbp = (MimeBodyPart)part;
if (mbp.isMimeType("text/plain")) {
// Handle plain
} else {
// Special non-attachment cases here of
// image/gif, text/html, ...
}
...
}
8.处理HTML邮件
前面的例子中发送的邮件都是以文本为内容的(除了附件),下面将介绍如何接收和发送基于HTML的邮件。
A.发送HTML邮件
假如我们需要发送一个HTML文件作为邮件内容,并使邮件客户端在读取邮件时获取相关的图片或者文字的话,只要设置邮件内容为html代码,并设置内容类型为text/html即可:
String htmlText = "<H1>Hello</H1>" +
"<img src=/"http://www.jguru.com/images/logo.gif/">";
message.setContent(htmlText, "text/html"));
请注意:这里的图片并不是在邮件中内嵌的,而是在URL中定义的。邮件接收者只有在线时才能看到。
在接收邮件时,如果我们使用JavaMail API接收邮件的话是无法实现以HTML方式显示邮件内容的。因为JavaMail API邮件内容视为二进制流。所以要显示HTML内容的邮件,我们必须使用JEditorPane或者第三方HTML展现组件。
以下代码显示了如何使用JEditorPane显示邮件内容:
if (message.getContentType().equals("text/html")) {
String content = (String)message.getContent();
JFrame frame = new JFrame();
JEditorPane text = new JEditorPane("text/html", content);
text.setEditable(false);
JScrollPane pane = new JScrollPane(text);
frame.getContentPane().add(pane);
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.show();
}
B.在邮件中包含图片
如 果我们在邮件中使用HTML作为内容,那么最好将HTML中使用的图片作为邮件的一部分,这样无论是否在线都会正确的显示HTML中的图片。处理方法就是 将HTML中用到的图片作为邮件附件并使用特殊的cid URL作为图片的引用,这个cid就是对图片附件的Content-ID头的引用。
处理内嵌图片就像向邮件中添加附件一样,不同之处在于我们必须通过设置图片附件所在的邮件体部分的header中Content-ID为一个随机字符串,并在HTML中img的src标记中设置为该字符串。这样就完成了图片附件与HTML的关联。
String file = ...;
// Create the message
Message message = new MimeMessage(session);
// Fill its headers
message.setSubject("Embedded Image");
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
// Create your new message part
BodyPart messageBodyPart = new MimeBodyPart();
String htmlText = "<H1>Hello</H1>" +
"<img src=/"cid:memememe/">";
messageBodyPart.setContent(htmlText, "text/html");
// Create a related multi-part to combine the parts
MimeMultipart multipart = new MimeMultipart("related");
multipart.addBodyPart(messageBodyPart);
// Create part for the image
messageBodyPart = new MimeBodyPart();
// Fetch the image and associate to part
DataSource fds = new FileDataSource(file);
messageBodyPart.setDataHandler(new DataHandler(fds));
messageBodyPart.setHeader("Content-ID","<memememe>");
// Add part to multi-part
multipart.addBodyPart(messageBodyPart);
// Associate multi-part with message
message.setContent(multipart);
9.在邮件中搜索短语
JavaMail API提供了过滤器机制,它被用来建立搜索短语。这个短语由javax.mail.search包中的SearchTerm抽象类来定义,在定义后我们便可以使用Folder的Search()方法在Folder中查找邮件:
SearchTerm st = ...;
Message[] msgs = folder.search(st);
下面有22个不同的类(继承了SearchTerm类)供我们使用:
AND terms (class AndTerm)
OR terms (class OrTerm)
NOT terms (class NotTerm)
SENT DATE terms (class SentDateTerm)
CONTENT terms (class BodyTerm)
HEADER terms (FromTerm / FromStringTerm, RecipientTerm / RecipientStringTerm, SubjectTerm, etc.)
使用这些类定义的断语集合,我们可以构造一个逻辑表达式,并在Folder中进行搜索。下面是一个实例:在Folder中搜索邮件主题含有“ADV”字符串或者发信人地址为friend@public.com的邮件。
SearchTerm st =
new OrTerm(
new SubjectTerm("ADV:"),
new FromStringTerm("friend@public.com"));
Message[] msgs = folder.search(st);
六、参考资源
JavaMail API Home
Sun’s JavaMail API基础
JavaBeans Activation Framework Home
javamail-interest mailing list
Sun's JavaMail FAQ
jGuru's JavaMail FAQ
Third Party Products List
七、代码下载
http://java.sun.com/developer/onlineTraining/JavaMail/exercises.html
JavaMail常见问题
一般问题
-
- 什么是 JavaMail API?
- 我如何获得 JavaMail API 的实现?
- 我如何发送反馈或评论?
- javax.activation 在哪里?
- JavaMail API 需要什么 JDK?
- 从哪里能找到我喜欢的操作系统的 JavaMail 版本?
- JavaMail API 可以在浏览器中工作吗?
- 我可以使用 JavaMail 来从 Web 邮件账户(比如 Yahoo 或 Hotmail)中阅读邮件吗?
- 什么是 IMAP?
- 什么是 SMTP?
- 什么是 MIME?
- 什么是 POP3?
- 对 MAPI、NNTP、Lotus Notes 和其他服务提供程序的支持怎样?
- 我如何在本地磁盘上存储邮件消息?
- 我可以从哪里找到有关 Sun 协议提供程序的文档?
- JavaMail API 实现是否完全免费?我可以把它与我的产品一起发行吗?
- 我可以获得 JavaMail API 实现的源代码吗?
- JavaMail 是否符合 Y2K?
- 我的问题不在这里,我应该在哪里查找?
- 什么是 JavaMail API?
安装和配置
-
- 我如何安装 JavaMail API 实现?
- JavaMail 是否包括所有必要的邮件服务器?
- 从哪里可以获得必要的邮件服务器?
- 我应该使用什么主机名、用户名或密码?
- 我如何配置 JavaMail 通过代理服务器工作?
- 如何在 Windows NT 中设置 CLASSPATH?
- 当试图在 Linux 中运行程序时,得到了非常奇怪的错误消息,而且程序运行失败了。错误在哪里?
- 在运行于 SecurityManager 下面的应用程序中,我如何使用 JavaMail;我必须授予应用程序和 JavaMail 什么权限?
- 如何配置 Web 服务器来运行 JavaMail 演示 servlet?
- 当在 servlet 中使用 JavaMail 时,未找到任何的 JavaMail 类。我已经在服务器的 CLASSPATH 中添加了 mail.jar?
- 我的 servlet 可以找到 JavaMail 类,但 JavaMail 抱怨它不能找到针对“smtp”或“imap”的服务提供程序或地址类型“rfc822”。
- 在哪里可以找到 jws.jar?我已经安装了 Java Web Server 2.0,并试图运行 JavaMailServlet。README 文件指示我在 CLASSPATH 中添加 jws.jar。
- 我如何安装 JavaMail API 实现?
编程
-
- 从哪里可以学习有关 Internet 邮件的基础知识,它是我高效编写 JavaMail 程序需要知道的?
- 如何调试使用 JavaMail API 的应用程序?
- 如何发送带有附件的消息?
- 如何阅读带有附件的消息并保存附件?
- 什么是“中断支持”(disconnected support)?
- 如何使用 JavaMail API 来支持中断操作?
- 我如何使用 JavaMail API 来发送安全的电子邮件?
- writeTo() 方法生成了消息文件,但消息中的一些行既不是数据的规范 MIME 表示(即使用 CRLF 来结束行),又没有使用我的平台的规范行分隔符(例如 UNIX 上的“/n”)。如果我需要这些表示时,如何获得它们当中的任何一种表示?
- 我可以使用 JavaMail API 来实现邮件服务器吗?
- 我可以使用 JavaMail API 在我的邮件服务器上添加新的用户账户、删除用户账户或改变用户账户的密码吗?
- 为什么 MimeMessage 类没有实现 Serializable,这样我就可以将消息序列化到磁盘,并在以后读回它?
- 我如何编写服务提供程序?
- 我在登录 Microsoft Exchange 服务器时碰到了麻烦,我确信正在使用的用户名和密码是正确的,我做错了什么?
- 在发送二进制文件前,我如何编码它?当收到它时,我又如何解码它?
- 如果我不需要自己编码和解码附件,我应该何时使用 MimeUtility 方法?
- 尽管 JavaMail 完成了所有的编码和解码工作,但我仍需要手动控制一些主体部分的编码。
- 为什么 JavaMail 没有在非 ASCII 字符集中正确编码和解码文件名?
- 从哪里可以学习有关 Internet 邮件的基础知识,它是我高效编写 JavaMail 程序需要知道的?
阅读邮件——IMAP
-
- 我尝试在我的 IMAP 服务器上运行演示程序,但我得到了错误。
- 当我提取带有较大附件的消息时,IMAP 提供程序似乎丢失了数据。
- IMAP 提供程序是否缓存检索的数据?
- 我想在文件夹间移动消息。我应该使用 appendMessages() 或 copyMessages() 吗?
- 有时检索大消息主体似乎是低效的。
- 当加载这种大二进制附件时,我得到了 OutOfMemory 错误。
- 当在主体部分 (bodypart) 上调用 getContent() 时,我为什么会得到 UnsupportedDataTypeException?
- 当在包含文本数据的主体部分 (bodypart) 上调用 getContent() 时,为什么会得到 UnsupportedEncodingException?
- 我尝试在我的 IMAP 服务器上运行演示程序,但我得到了错误。
发送邮件——SMTP
-
- 我如何回复消息?
- 我如何转发消息?
- 我如何发送 HTML 邮件?
- 我如何发送具有不同字体和颜色的格式化文本的邮件?
- 我如何发送具有纯文本和 HTML 文本的邮件,让每个邮件的阅读者可以选择适合它的格式?
- 我如何发送包含图像的 HTML 邮件?
- Transport 方法 send 和 sendMessage 之间有什么区别?
- 我需要验证到 SMTP 服务器,因此我调用了 trans.connect(host, user, password),然后调用 trans.send(msg) 发送消息,但它却不能工作。
- 我修改了一条消息,但标题却没有反映修改。
- 我正在使用 sendMessage() 方法发送消息,但在消息中的文本前后却出现奇怪的一些行,并且我的附件也在消息体中出现。
- 我为新消息的 Message-ID 标题设置了特定值。但当我发送这条消息时,却重写了那个标题。
- 当发送创建的新消息时,为什么会得到 UnsupportedDataTypeException?
- 当发送消息时,如何能够显式地设置 SMTP FROM: 属性?
- 我想重复发送消息,并且每次发送给一组不同的收件人。但调用 Transport.send(Message) 却导致每次都创建一个新的 Transport 会话。在本例中,这是一个次优办法,我如何来解决它?
- 当试图发送消息时,我得到了 “MessagingException: 501 HELO requires domain address”(MessagingException: 501 HELO 要求域地址)。
- 如果将消息发送到错误的地址,为什么我会获得 SendFailedException 或 TransportEvent,指出地址是错误的?
- 当消息不能被传送时,就会返回一个失败消息。我如何检测这些“回弹”消息?
- 当创建 InternetAddress 对象时,如果地址是非法的,为什么不会获得异常?
- 当试图发送消息时,我为什么会获得 javax.mail.SendFailedException: 550 Unable to relay for 我的地址?
- 当试图发送消息到(例如)Yahoo时,为什么我会获得一个错误,指出“连接被拒绝”?
- 当 发送邮件时,为什么会失败,并得到一个异常,异常中包括像“553 To send mail, first check your mail with a valid POP account”(553 为发送邮件,首先检查邮件具有合法的 POP 账户)这样的消息?
- 我如何回复消息?
POP3
-
- 我可以从哪里找到 POP3 的支持?
- 我想在 POP3 服务器上删除消息。我在这些消息上设置了 DELETED 标志。然后调用 expunge() 方法,但我得到了MethodNotSupportedException。当使用 Sun 的 POP3 提供程序时,我如何删除消息?
- 在从 Sun POP3 提供程序获得的消息中,我如何可以检索 POP3 UIDL?
- 对于 POP3,我如何知道哪里消息是新的?
- 当使用 POP3 时,为什么 hasNewMessages() 总是返回 false?
- 我创建了 MessageCountListener(如 monitor 程序中所展示的),但在我的 POP3 INBOX(收件箱)中,我从未得到通知。
- 当使用 POP3 时,为什么 getReceivedDate() 返回 null?
- 当使用 POP3 时,我得到了有关 SocketFetcher 类的抱怨。
- 当使用 POP3 时,我得到了有关 contentStream 字段的抱怨。
- 除了 POP3 服务器上的 INBOX(收件箱)外,您如何访问或创建文件夹?
- 我可以从哪里找到 POP3 的支持?
servlet 中的 JavaMail
applet 中的 JavaMail
![]() |
一般问题
问. 什么是 JavaMail API?
答:JavaMail API 是一组抽象的 API,它可用于构建邮件系统。API 提供了一个平台无关以及协议无关的框架,用于构建基于 Java 技术的电子邮件客户端应用程序。JavaMail API 提供了一些工具,用于阅读和发送电子邮件。服务提供程序实现了特定的一些协议。在 JavaMail API 软件包中,包括了几个服务提供程序;另外还有其他的一些服务提供程序可供使用。JavaMail API 是作为 Java 可选包实现的,它可用在 JDK 1.1.6 中,以后还可用在任何操作系统上。JavaMail API 也是 Java 2 Platform, Enterprise Edition (J2EE) 中的必需部分。
问. 我如何获得 JavaMail API 的实现?
答: Sun 提供了无版权的参考实现(二进制形式),开发人员可以使用和发行。参考实现包括核心的 JavaMail 包 和 IMAP、POP3 和 SMTP 服务提供程序。
问. 我如何发送反馈或评论?
答:发送电子邮件到 javamail@Sun.COM。也可以考虑加入我们的低容量通告邮件列表 (low-volume announce mailing list) javamail-announce@java.sun.com。在我们的 Web 页 http://java.sun.com/products/javamail 中有关于如何加入的说明。
问. javax.activation 在哪里?
答: javax.activation
是 JavaBeans Activation Framework (JAF) 的一部分。JAF 是 JavaBeans "Glasgow" 规范的一部分(参见 http://java.sun.com/beans/glasgow/jaf.html)。JavaMail API 将 JAF 用于数据内容处理。
问. JavaMail API 需要什么 JDK?
答:JavaMail API 要求 JDK/JRE 1.1.x 或更高版本。我们已经在 Sun Microsystems JDK 1.1.6 至 JDK 1.1.7 上测试过这个实现,而这些 JDK 运行于 Solaris SPARC、Microsoft Windows 95/NT 和 RedHat Linux 4.2。我们也已经在 Java 2 SDK, Standard Edition (J2SE SDK) 版本 1.2、1.3 和 1.4 上做过测试,而这些 JDK 运行于 Solaris SPARC。JavaMail API 是 Java 的可选包,它们不是核心 J2SE 的一部分,但在 J2EE 中却包括了它。参见 http://java.sun.com/j2se,获取关于 JDK 1.1 和 J2SE 的进一步信息。
问. 从哪里能找到我喜欢的操作系统的 JavaMail 版本?
答:JavaMail 是完全用 Java 编写的,并将运行在特定的操作系统上,该操作系统支持所需版本的 JDK。对于不同的操作系统,不需要任何特殊的版本。
问. JavaMail API 可以在浏览器中工作吗?
答:在 Netscape 和 Internet Explorer 中,我们已经成功运行过 JavaMail 1.3。
问. 我可以使用 JavaMail 来从 Web 邮件账户(比如 Yahoo 或 Hotmail)中阅读邮件吗?
答:不 一定。许多基于 Web 的电子邮件服务只提供在浏览器中用 HTTP 进行访问。这些服务不能使用 JavaMail 来访问。如果服务也提供 POP3 或 IMAP 访问,可能不可以使用 JavaMail 来访问它。 联系你的 Web 电子邮件服务提供商,获取更详细的信息。也可参阅 JHTTPMail 项目,获得 JavaMail 的开放源代码提供程序,它提供了对 Hotmail 的访问。另一个有用的工具是 MrPostman——提供到 Hotmail 和 Yahoo! Mail 的 POP3 访问的代理服务器,这个工具可从如下站点获得:http://mrbook.org/mrpostman/。
问. 什么是 IMAP?
答:IMAP 是 Internet 消息访问协议 (Internet Message Access Protocol)。它是访问电子邮件消息的一种方法,而这些消息存储在(可能是共享的)邮件服务器上。换句话说,它允许“客户端”电子邮件程序访问远程 的消息存储,并且在访问时,就像它们就在本地一样。IMAP 由 RFC2060 定义。有关进一步信息,请参阅 http://www.imap.org/。
问. 什么是 SMTP?
答:SMTP 就是简单邮件传输协议 (Simple Mail Transfer Protocol)。它可用于不同邮件主机间 RFC822 样式的消息的传输,以及用于将新消息提交给一台主机,然后由它来传送该消息。SMTP 在广泛使用中(起源于 1982 年),并由 RFC821 定义。
问. 什么是 MIME?
答:MIME 和 RFC822 是一些标准,它们用于描述电子邮件消息,而这些消息是跨 Internet 进行发送的。javax.mail.internet 子包(它是 JavaMail API 的一部分)提供了这两个包的完整实现。MIME 是由下述 RFC 指定的:RFC2045、RFC2046 和 RFC2047。
问. 什么是 POP3?
答:POP3 是邮局协议 (Post Office Protocol V3)。POP3 是一个非常有限的协议,用于访问单个邮箱。它在功能上比 IMAP 差了许多。POP3 应用非常广泛,并由 RFC1939 定义。
问. 对 MAPI、NNTP、Lotus Notes 和其他服务提供程序的支持怎样?
答:目前我们还没有计划实现这些提供程序中的任何一个。不过,JavaMail API 的体系结构提供一些功能,可以容易地插入第三方服务提供程序。我们的希望是第三方将通过编写针对其他标准和专有协议的提供程序来包括 JavaMail API。 参阅 第三方产品 页面,获取这些提供程序的最新列表。
问. 我如何在本地磁盘上存储邮件消息?
答:“本地存储提供程序”可用于将邮件消息存储在本地磁盘中。JavaMail API 不包括这一提供程序,但有几个提供程序可从第三方获得,它们针对不同的本地存储格式,比如 MH 和 Mbox。参阅我们的 第三方产品 页面,获取这些提供程序的最新列表。
问. 我可以从哪里找到有关 Sun 协议提供程序的文档?
答:针对 IMAP、POP3 和 SMTP 的 Sun 协议提供程序支持许多特性,它们不是 JavaMail API 规范的一部分。在 JavaMail 1.3 下载包中包括了这些协议提供程序的文档,这些文档是以 javadoc 格式包括的,并位于 docs/javadocs
目录。针对每个协议提供程序包的包级文档描述了一些属性,这些属性由协议提供程序支持。此外,协议提供程序包括了一些类和方法,应用程序可以使用它们来利用特定于提供程序的特性。注意,这些属性、类和方法的使用使得程序不可移植;它可能只与 Sun 的 JavaMail API 实现一起工作。
问. JavaMail API 实现是否完全免费?我可以把它与我的产品一起发行吗?
答:可以。当前版本的 JavaMail API 实现是完全免费的,你可以在产品中包括它。这一版本也包括 IMAP、POP3 和 SMTP 提供程序。请一定阅读 许可,并保证理解它。JavaBeans Activation Framework 也是在类似的许可下免费使用的。
问. 我可以获得 JavaMail API 实现的源代码吗?
答:在 SCSL 许可下,现在 JavaMail API 参考实现源代码可作为 J2EE 的一部分获得。可从 这里 下载它。
问. JavaMail 是否符合 Y2K?
答:是的。 Sun 的 JavaMail 实现是符合 Option-3 Y2K 的。请参看 JavaMail 包中的 NOTES.txt 文件,获取进一步信息。
问. 我的问题不在这里,我应该在哪里查找?
答:除 JavaMail API 规范和 javadocs 外(可从 我们的 Web 主页 获得并且是包括在下载包中的),不要忘记查阅 JavaMail API 包中包括的 README.txt 和 NOTES.txt 文件,获取其他的重要信息。
javamail-interest mailing list 是另一个信息来源。参阅 我们的 Web 主页,获取有关如何订阅的说明以及指向文档的链接。
Java Developer Connection 维护 JavaMail 论坛,请参阅 http://forum.java.sun.com。注意,JavaMail 团队管理 javamail-interest 邮件列表,但不管理 JavaMail 论坛。
jGuru 也维护 JavaMail FAQ,可在如下地址找到它:http://www.jguru.com/faq/JavaMail。
安装和配置
问. 我如何安装 JavaMail API 实现?
答:解 压缩分发的 zip 文件,并编辑 CLASSPATH 环境变量以包含 mail.jar 文件,该文件包括在 JavaMail API 分发中。你也需要 JavaBeans Activation Framework(参见下面)的实现。参阅 README 文件(在分发中),获得其他细节和例子。
问. JavaMail 是否包括所有必要的邮件服务器?
答:不 是,JavaMail API 包不包括任何邮件服务器。为了使用 JavaMail API 包,你将需要访问 IMAP 或 POP3 邮件服务器(用于阅读邮件)和/或 SMTP 邮件服务器(用于发送邮件)。这些邮件服务器通常由 Internet 服务提供商提供,或者作为组织网络基础结构的一部分。如果你不必访问这样的邮件服务器,请往下看。
问. 从哪里可以获得必要的邮件服务器?
答:Sun Java System Messaging Server 可用于 Solaris 和 Windows 平台。华盛顿大学的 IMAP 服务器支持多种平台 (UNIX、32 位 Windows 等)。可从如下地址获取源代码:ftp://ftp.cac.washington.edu/imap/imap.tar.Z。其他的许多供应商提供了支持 Internet 标准的邮件服务器。可以从 IMAP Connection 和 Internet Mail Consortium 获得更多信息。
问. 我应该使用什么主机名、用户名或密码?
答:我们不提供邮件服务器让你使用。你必须使用自己的邮件服务器,或者使用 Internet 服务提供商或你所工作的公司提供的邮件服务器。网络管理员可能给你一些必要的信息用于配置 JavaMail,以便同邮件服务器一起工作。
问. 我如何配置 JavaMail 通过代理服务器工作?
答:大 多数代理服务器只支持 HTTP 协议。JavaMail 没有使用 HTTP 协议来阅读或发送邮件。使用代理服务器的一个主要原因是为了允许企业网络中的 HTTP 请求通过企业防火墙。防火墙通常会阻止对 Internet 的大多数访问,但允许来自代理服务器的请求通过。此外,企业网络内部的邮件服务器将为邮件执行类似的功能,通过 SMTP 接收消息,然后将它们转发到 Internet 上的最终目的地,以及接收传入的消息,然后将它们发送到合适的内部邮件服务器。
如果你的代理服务器支持 SOCKS V4 或 V5 协议 (http://www.socks.nec.com/aboutsocks.html, RFC1928),并允许匿名连接,可以告诉 Java 运行时把所有的 TCP socket 直接连接到 SOCKS 服务器。参阅 http://java.sun.com/j2se/1.4/docs/guide/net/properties.html,获取 socksProxyHost
和 socksProxyPort
属性的最新文档。这些是系统级属性,而不是 JavaMail 会话属性。当调用应用程序时,它们可以从命令行中设置,例如:java -DsocksProxyHost=myproxy ...
。这个工具可用于指出从 JavaMail 到 SOCKS 代理服务器进行 SMTP、IMAP 和 POP3 通信。注意,设置这些属性将告诉 所有 TCP socket 连接到 SOCKS 代理,在应用程序的其他方面上,这可能会带来负面影响。
假如没有这样的 SOCKS 服务器,如果想使用 JavaMail 来直接访问防火墙外部的邮件服务器,那将需要配置防火墙来允许这一访问。一个简单的 HTTP 代理 Web 服务器是足够的。
问. 如何在 Windows NT 中设置 CLASSPATH?
答:详细说明可从 这里 获得。
问. 当试图在 Linux 中运行程序时,得到了非常奇怪的错误消息,而且程序运行失败了。错误在哪里?
答:通常,错误消息看起来像下面这样:
Exception in thread "main"
java.lang.VerifyError:(Class:com/sun/mail/pop3/POP3Store,
method: finalize Signature :()V)
Illegal use of nonvirtual function call
问题是由于在 Linux 上,使用的 unzip 命令是有 bug 的版本,这样解压缩 JavaMail 下载包时,unzip 命令破坏了 mail.jar 文件。获取更新版本的 unzip 命令或使用 JDK 的 jar 命令来解压缩下载包。
问. 在运行于 SecurityManager 下面的应用程序中,我如何使用 JavaMail;我必须授予应用程序和 JavaMail 什么权限?
答:在 具有 SecurityManager 的 JDK 1.2(或更新版本)中,当使用 JavaMail 时,JavaMail 读取 mail.jar 文件中的配置文件有时会失败。在从 activation.jar 文件中读取配置文件时,JavaBeans Activation Framework 可能也有相同的问题。这些默认配置文件是作为“资源”文件存储的,并且存储在 jar 文件的 META-INF 目录中。
有许多调试技术可用于决定这是否是个问题。设置 Session 属性“mail.debug”为 true(或调用 session.setDebug(true)
), 将导致 JavaMail 在试图加载各个配置文件时打印调试消息。形如“DEBUG: can't load default providers file”(DEBUG: 不能加载默认提供程序文件) 的消息指出这个问题可能存在。同样,设置 System 属性“javax.activation.debug”为“true”(例如,通过使用 "java -Djavax.activation.debug=true ..." 来运行程序),将导致 JAF 在试图加载各个资源文件时打印调试消息。最后,通过设置 system 属性“java.security.debug” 为“access:failure”(例如,通过使用“java -Djava.security.debug=access:failure ...”来运行程序),JDK 可以产生有用的调试输出。
除了读取配置文件的必要权限外,应用程序(和 JavaMail)也将需要一定的权限才可以连接到它使用的邮件服务器。如果应用程序使用 System 属性来配置 JavaMail(例如,像许多 JavaMail 演示程序所做的那样,通过传递从 System.getProperties()
中返回的 Properties 对象到 Session 构造函数),它也将需要一定的权限才可以使用 System Properties 对象。另外,应用程序可以使用自己的 Properties 对象,以及确信设置 "mail.from" 属性 或 "mail.user" 和 "mail.host" 属性(参见 InternetAddress.getLocalAddress() 方法)。
在 JDK 1.2 SecurityManager 中,为了使应用程序能够使用 JavaMail,应用程序、JavaMail 和 JAF 将需要某些权限,比如下面的一些权限(一定要使用适当的值替换主机名和路径名);把这些权限添加到应用程序使用的安全策略文件中。
grant { |
问. 如何配置 Web 服务器来运行 JavaMail 演示 servlet?
答:针对以下 Web 服务器的指导说明可从这里获得:
问. 当在 servlet 中使用 JavaMail 时,未找到任何的 JavaMail 类。我已经在服务器的 CLASSPATH 中添加了 mail.jar?
答:当改变 CLASSPATH 时,通常有必要完全重启 Web 服务器。
问. 我的 servlet 可以找到 JavaMail 类,但 JavaMail 抱怨它不能找到针对“smtp”或“imap”的服务提供程序或地址类型“rfc822”。
答:通常这是因为 JavaMail 无法访问 mail.jar 中的配置文件,而这可能是由于安全权限问题造成的;参见 本条目,获取更多的细节。也保证你没有提取 mail.jar 内容;在服务器的 CLASSPATH 中,应该包括未更改的 mail.jar 文件。
问. 在哪里可以找到 jws.jar?我已经安装了 Java Web Server 2.0,并试图运行 JavaMailServlet。README 文件指示我在 CLASSPATH 中添加 jws.jar。
答:jws.jar 不再与 Java Web Server 一起发行(在以前版本中,它们是一起发行的),因此不需要在 CLASSPATH 中添加它。只要在 CLASSPATH 中添加 mail.jar 和 activation.jar,然后重启 Java Web Server。
编程
问. 从哪里可以学习有关 Internet 邮件的基础知识,它是我高效编写 JavaMail 程序需要知道的?
答:参见我们的 Web 页中提及的 参考图书 中的一本,获得有关 Internet 电子邮件、MIME、SMTP、IMAP 和 POP3 等的背景信息。
问. 如何调试使用 JavaMail API 的应用程序?
答:可通过在代码中调用 Session 对象上的 setDebug(true)
方法来打开调试模式。这将造成在控制台中打印调试消息,包括协议跟踪信息。如果您认为在 JavaMail 中发现了 bug,就将这个跟踪及以下信息发送给我们:重生成问题的测试用例、使用的平台、使用的 JDK 的版本和使用的邮件服务器 (IMAP, SMTP) 的名称和版本。
问. 如何发送带有附件的消息?
答:带有附件的消息采用 MIME 多部分消息来表示,其中第一部分是消息的主体,其他的部分是附件。有大量的例子,它们展示了如何在演示程序中构建这种消息,JavaMail 下载包包含了这些演示程序。
问. 如何阅读带有附件的消息并保存附件?
答:如前面描述,在 MIME 中,带有附件的消息是作为多部分消息表示的。在简单的例子中,Message
对象的 getContent
方法的结果将是一个 MimeMultipart
对象。多部分对象的第一个主体部分将是消息的主文本。其他的主体部分将是附件。msgshow.java
演示程序展示了如何在消息中遍历所有的多部分对象,并提取各个主体部分数据。getDisposition
方法将给你一个提示,指出主体部分是否应该内嵌显示,或者应该将其考虑成附件(但请注意,不是所有的发件人都提供这一信息)。
为了将主体部分中的数据保存到文件(比如),请使用 getInputStream
方法来访问附件内容,并将数据复制到 FileOutputStream
。
注意,也有一些更复杂的情形也要处理。例如,一些发件人把主体作为纯文本和 html 发送。通常,这将作为 multipart/alternative
内容(和 MimeMultipart
对象)出现在简单的文本主体部分的位置中。经过数字签名或加密的消息甚至会更复杂。处理所有的这些情形可能具有挑战性。请参考各种 MIME 规范和我们主页上列出的其他 参考资料。
问. 什么是“中断支持”(disconnected support)?
答:支 持中断操作的邮件客户端将允许用户访问远程消息存储(比如 IMAP)中消息,缓存这些消息中的一些消息的(部分)到本地,然后断开到服务器的连接。当处在断开连接状态中,邮件客户端可以访问已经缓存的消息,可能 也可以删除它们或将它们保存到其他文件夹。当邮件客户端下一次连接到远程消息存储时,本地所做变更会与远程存储同步。同样,中断支持可以允许客户端在断开 与服务器连接时“发送”消息,在到服务器的连接可用前,消息会进行排队。也请参阅 RFC1733。
问. 如何使用 JavaMail API 来支持中断操作?
答:JavaMail API 规范定义了一些接口,邮件客户端可以使用这些接口来支持中断操作。我们的 IMAP 提供程序实现了这些接口(UIDFolder 接口)。
问. 我如何使用 JavaMail API 来发送安全的电子邮件?
答:JavaMail API 目前不支持发送或接收安全电子邮件。JavaMail API 的体系结构使得可以在以后很容易地添加这一支持,我们可以添加支持,第三方也可以添加支持。有关当前电子邮件安全标准(S/MIME 和 PGP)的信息,可以从如下站点找到: http://www.imc.org/smime-pgpmime.html。 请浏览我们的 第三方产品 页,获取来自其他供应商的解决方案。
问. writeTo() 方法生成了消息文件,但消息中的一些行既不是数据的规范 MIME 表示(即使用 CRLF 来结束行),又没有使用我的平台的规范行分隔符(例如 UNIX 上的“/n”)。如果我需要这些表示时,如何获得它们当中的任何一种表示?
答:不管是哪种情形,你都将需要创建合适的 FilterOutputStream 对象来传递给 writeTo()
。FilterOutputStream 将需要接受特定的一些行,它们具有任何常见终止符,然后写出另外的一些特定行,它们只具有期望的行终止符。下面是这种过滤器的一些例子。NewlineOutputStream
转换到本地平台的行终止符,当将消息写到文件时,它是有用的。CRLFOutputStream
转换到 MIME 规范 CRLF 终止符,当需要规范 MIME 格式时(比如计算数字签名),它是有用的。
问. 我可以使用 JavaMail API 来实现邮件服务器吗?
答:JavaMail API 设计目的不是帮你实现邮件服务器。但是,对你来说,一些实用工具类,比如 MIME 消息解析类,可能是有用的。通常您会发现,JavaMail API 是在“简单”而不是在“强有力”方面出错。对于邮件客户端,那是合适的,但对于邮件服务器,结果可能是不同的。
问. 我可以使用 JavaMail API 在我的邮件服务器上添加新的用户账户、删除用户账户或改变用户账户的密码吗?
答:JavaMail API 不包括任何工具,用于添加、删除或修改用户账户。在这一方面是没有标准的,每个邮件服务器对它的处理是不同的。
问. 为什么 MimeMessage 类没有实现 Serializable,这样我就可以将消息序列化到磁盘,并在以后读回它?
答:JavaMail API 是在现有电子邮件系统上面设计的,并使用了现有的消息格式。对于这样的一些实现,使用 Java 序列化能力既不是必要的,也不是有用的,因此,不能将它作为 JavaMail API 的目标来考虑。
有关序列化 Message 的困难部分是保留某些指针,它们指向 Folder(文件夹)、Store(存储)和 Session(会话)。如果只想保存消息的内容,而不是对象本身,消息的 writeTo
方法将为你完成这一切。如果想根据序列化消息创建整个电子邮件系统,您应该能够编写 Message 等的子类,并在子类中实现 Serializable。
如果想序列化自己的引用了 MimeMessages的其他对象,那么你的对象的 writeObject
方法可以使用 MimeMessage 的 writeTo
方法,对象的 readObject
方法可以使用 MimeMessage 构造函数,该构造函数会得到 InputStream。在构造 MimeMessage 时,你的类将需要提供一个 Session。
问. 我如何编写服务提供程序?
答:请阅读服务提供程序文档,获取一些细节信息。通常,如果想编写 Store 提供程序,那就编写 javax.mail.Store
和 javax.mail.Folder
的子类,也可能要编写 javax.mail.Message
及其他一些类的子类。对于 Transport 提供程序,编写 javax.mail.Transport
的子类,也可能需要编写 javax.mail.Message
及其他的一些类的子类。然后在 javamail.providers
注册表中,添加描述提供程序的条目。如果对编写特别的服务提供程序感兴趣,并且它所针对的协议或邮件系统目前没有得到 JavaMail API 实现的支持,请通过 javamail@Sun.COM 联系我们。
问. 我在登录 Microsoft Exchange 服务器时碰到了麻烦,我确信正在使用的用户名和密码是正确的,我做错了什么?
答:当登录 Exchange 时,需要使用比简单登录名更多的用户名。例如,如果你的电子邮件地址是“J.User@server.com”,Windows NT 登录名是“juser”,NT 域名是“dom”,而且 Exahange 邮箱名是“Joe User”,那么在使用 JavaMail 登录时,你将需要使用用户名 “dom/juser/J.User”。
问. 在发送二进制文件前,我如何编码它?当收到它时,我又如何解码它?
答:你不必这样的做!JavaMail 将自动决定合适的编码用于消息部分,然后才发送消息。而且当读取它们时,将自动解码消息部分。getInputStream
方法将返回解码数据。
问. 如果我不需要自己编码和解码附件,我应该何时使用 MimeUtility 方法?
答:在 JavaMail 没有自动处理的情况下,MimeUtility
方法是有用的。经常发生的这样的一种情形是文件名的编码。基本的 MIME spec(规范)不允许按某种方式编码标题参数值(比如文件名参数),该方式与(比如)编码 Subject(主题)标题相同。这限制了参数值,从而限制了文件名到 ASCII。但一些发件人却实际使用 MIME 文本编码来做非 ASCII 文件名的编码工作。想与这种非标准发件人互操作的应用程序可以使用 encodeText
方法来编码文件名,然后调用 MimeBodyPart
setFileName
方法,而且可以使用 decodeText
方法来解码返回的文件名。
问. 尽管 JavaMail 完成了所有的编码和解码工作,但我仍需要手动控制一些主体部分的编码。
答:在少数的场合需要控制编码,这里有几个方法来重写 JavaMail 的默认行为。下面是一个简单的方法。在创建整个消息后,调用 msg.saveChanges(),
然后使用像 mbp.setHeader("Content-Transfer-Encoding", "base64")
的语句来强制对指定主体部分做 base64 编码。
另一种办法是编写 MimeBodyPart
的子类,并重写 updateHeaders
方法,让它首先调用 super.updateHeaders()
,然后像上面那样设置 Content-Transfer-Encoding
标题。
问. 为什么 JavaMail 没有在非 ASCII 字符集中正确编码和解码文件名?
答:文件名是作为参数存储在 MIME 标题中的。形如 =?ISO-8859-15?B?5OTkLUluZm8ucGRm?=
的编码文件名不是 MIME spec(规范)的一部分。形如 =?A?B?C?=
的文件名是一个完全有效的文件名,而不是一个不正确编码的文件名。JavaMail 没有编码和解码文件名,因为这样做会违反 MIME 规范。
基本的 MIME 规范不允许编码参数。RFC 2231 定义了一种新的方式,使得可以在 MIME 标题中包括编码参数,包括文件名。它与下面的事实方式不兼容:许多应用程序非法编码文件名。支持 RFC 2231 将不允许 JavaMail 与这些现有的程序互操作。据我所知,很少现有的程序支持 RFC 2231。
如果你选择违反 MIME 规范是为了与其他也违反了 MIME的程序互操作,那么 JavaMail 会给您所有需要的工具来完成这件事。
编码文件名的解决方法是简单的:
mbp.setFileName(MimeUtility.encodeText(filename));
解码文件名的解决方法同样简单:
String filename = MimeUtility.decodeText(part.getFileName());