最近需要實現一個使用Spring schedule按一定時間間隔自動觸發條件發送郵件的功能,在開發的過程中,是按照先測試能發出text/html文本郵件,然后測試添加附件發送郵件,我碰到的問題是,文本郵件能正常發送出來,但是添加附件的郵件卻發不出來,這個問題困擾了我很久,所以有必要記錄下。
問題點
報錯內容:"javax.activation.UnsupportedDataTypeException: no object DCH for MIME type multipart/mixed"
初步解釋:是不支持數據類型,沒有復合郵件對象的專用信息渠道(MIME type multipart/mixed)”,字面意思是沒有發送MIME類型郵件的對象專用信息通道。
報錯內容

以下是百度對MIME郵件的解釋:
MIME(Multipurpose Internet Mail Extensions)多用途互聯網郵件擴展類型。是設定某種擴展名的文件用一種應用程序來打開的方式類型,當該擴展名文件被訪問的時候,瀏覽器會自動使用指定應用程序來打開。多用於指定一些客戶端自定義的文件名,以及一些媒體文件打開方式。
它是一個互聯網標准,擴展了電子郵件標准,使其能夠支持:非ASCII字符文本;非文本格式附件(二進制、聲音、圖像等);由多部分(multiple parts)組成的消息體;包含非ASCII字符的頭信息(Header information)
問題解決過程
a.發送郵件前可能缺少mime配置代碼
CSDN,overstackflow等上有很多帖子,說在使用實例化Transport對象send()方法發送郵件前,需要添加下面這一段代碼
MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap(); mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html"); mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml"); mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain"); mc.addMailcap("multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed; x-java-fallback-entry=true"); mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822"); CommandMap.setDefaultCommandMap(mc); Thread.currentThread().setContextClassLoader( getClass().getClassLoader() );
overstackflow上面對這段代碼的解釋是:
javaMail depends on some configuration files to map MIME types to Java classes (e.g., multipart/mixed
to javax.mail.internet.MimeMultipart
). These configuration files are loaded using the ClassLoader for the application. If the ClassLoader doesn't function properly, these configuration files won't be found
中文意思大概是:javaMail發送MIME類型的郵件需要依賴某些配置文件,比如如果使用MimeMultipart需要找到multipart/mixed的對應配置。這些配置文件將會被類裝載器加載,如果不能被正常加載,這些配置文件將無法生效。
javamail包下有MANFEST文件,里面有一個mailcap或者mailcap.default文件,讀取的就是這個文件,發現里面有對郵件格式的要求,如普通html郵件text/html,純文本郵件text/plain,包含附件的郵件multipart/*等,如下圖所示:

b.懷疑mail.jar包和activation.jar包版本問題
由於項目中使用的mail.jar包版本1.4.1,網上建議升級到1.4.7版本或更高的版本嘗試解決,由於項目服務器是使用OSGI來建立的,對OSGI幾乎不了解,使用常規的方式導入包后發現沒有任何效果,項目下只是新多了一個dependency libraries,里面包含自己添加的包,但是這個包里面的資源對於項目好像沒有效果,測試依然報錯。后面根據帖子又重新build path,將新jar包import然后又export后,還是沒有效果。
結果:包更換未成功,對OSGI不了解
參考博客:http://osgi.com.cn/article/7289374;
c.懷疑自己代碼本身存在問題
在嘗試了以上方法還是沒有解決后,參考博客 https://www.cnblogs.com/xdp-gacl/p/4216311.html,使用博客里能發出郵件附件的代碼來進行測試。測試時在OSGI服務端項目里新增了一個package,里面寫了一個包含main方法的類,直接發送郵件測試,以下是代碼
結果:將這部分代碼執行,發現能正常發出包含附件的郵件,然后將同樣的代碼放到OSGI服務器下運行,發現還是發不出來。通過以上發現,郵件發不出附件排除了jar包版本的問題,也可排除了代碼本身的問題(前面測試過了沒有問題),然后懷疑到了OSGI服務器設置的問題,查詢overstackflow上也有一個人跟我情況類似,參考博客:https://stackoverflow.com/questions/8923324/java-throws-an-unsupporteddatatypeexception-when-running-osgi-with-javax-mail?r=SearchResults,但是依然沒有找到解決辦法。
package TestMailSend;
import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; import javax.activation.DataHandler; import javax.activation.FileDataSource; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; public class SendMail { public static void main(String[] args) throws Exception{ //創建Properties文件
Properties prop=new Properties(); prop.setProperty("mail.host", "mail.boe.com.cn");//主機
prop.setProperty("mail.transport.protocol", "smtp"); prop.setProperty("mail.smtp.auth", "true"); //使用JavaMail發送郵件的5個step //step1 創建session //Session session=Session.getInstance(prop);
Session session1=Session.getDefaultInstance(prop); //開啟session的debug模式,可以看到程序發送email的運行狀態
session1.setDebug(true); //step2 通過session得到transport對象 //Transport trans=session.getTransport();
Transport trans=session1.getTransport(); //step3 使用郵件的用戶名和密碼連上郵箱服務器,發送郵件時,需要驗證發件人的郵箱名和密碼正確后才可以發送
trans.connect("mail.boe.com.cn", "發件人郵箱", "發件人郵箱密碼"); //step4 創建郵件 //Message message=createSimpleMail(session);//發送純文本
Message messageAttach=createAttachMail(session1); //step5 發送郵件 //trans.sendMessage(message, message.getAllRecipients());
trans.sendMessage(messageAttach, messageAttach.getAllRecipients());
trans.close(); } /** * 創建一個封只包含文本的郵件 * @param session * @return * @throws MessagingException * @throws AddressException */
public static MimeMessage createSimpleMail(Session session) throws AddressException, MessagingException{ //創建郵箱對象
MimeMessage message=new MimeMessage(session); //指明郵件的發件人
message.setFrom(new InternetAddress("發件人郵箱")); //指明郵件的收件人
message.setRecipient(Message.RecipientType.TO, new InternetAddress("收件人郵箱")); //郵件的標題
message.setSubject("只包含文本的簡單郵件"); //郵件的文本內容
message.setContent("這是一封測試郵件!","text/html;charset=utf-8"); //返回創建好的郵件對象
return message; } /** * 創建一封包含附件的郵件 * @param session * @return * @throws MessagingException * @throws AddressException * @throws IOException * @throws FileNotFoundException */
public static MimeMessage createAttachMail(Session session) throws AddressException, MessagingException, FileNotFoundException, IOException{ //創建郵件
MimeMessage message=new MimeMessage(session); //指明郵件發件人
message.setFrom(new InternetAddress("發件人郵箱")); //指明郵件收件人
message.setRecipient(Message.RecipientType.TO, new InternetAddress("收件人郵箱")); //郵件標題
message.setSubject("這是一封包含附件的郵件"); //創建郵件正文,為了避免郵件正文中文亂碼,使用utf-8
MimeBodyPart text=new MimeBodyPart(); text.setContent("使用JavaMail創建的包含附件的郵件","application/x-xls;charset=utf-8"); //創建附件郵件
MimeBodyPart attachment=new MimeBodyPart(); DataHandler dh=new DataHandler(new FileDataSource("C:\\測試Excel.xls")); attachment.setDataHandler(dh); attachment.setFileName(dh.getName()); //創建容器描述數據關系
MimeMultipart mmp=new MimeMultipart(); mmp.addBodyPart(text); mmp.addBodyPart(attachment); mmp.setSubType("mixed"); message.setContent(mmp); message.saveChanges(); //存盤
message.writeTo(new FileOutputStream("C:\\attachmentEmail.eml")); //返回生成的郵件
return message; } }
d.嘗試將mail包通過MANIFEST添加到class path里
最后通過MANIFEST,在runtime下添加了class path,將mail.jar包添加到類路徑,重新測試竟然OK了!
runtime下添加mail.jar類路徑。



Classpath下面有一段介紹:
Specify the libraries and folders that constitute the plug-in classpath. If unspecified, the classes and resources are assumed to be at the root of the plug-in.
英語蹩腳,大概意思就是,指定組成plug-in 類路徑下的文件夾,如本項目文件夾為lib。如果不指定,資源類可能還在plug-in的根目錄,這樣配置后,MANIFEST.MF里在Bundle-Class下面新增lib/mail.jar 。

然后在build.properties下發現自動新增lib/mail.jar。

網上有一篇帖子有大概的說明MANIFEST.MF配置,里面有一段話: .”指程序運行目錄,即導出的JAR 包所在目錄。程序運行時依據Class-Path 項的設置路徑來查找支持庫。每一個支持庫之間用空格隔開。
結論
將mail.jar包在MANIFEST文件下,通過runtime導入包的類路徑后,重新發送添加附件的郵件能成功。