posts - 78,  comments - 48,  trackbacks - 0
          發送郵件是web應用系統的一個基本功能。一般來說,郵件都有特定的類型,比如說密碼提醒,歡迎信息,訂單確認或者收信確認。盡管不同應用郵件的內容各不相同,但是發送郵件的過程基本上是一樣的。 構建消息,發送給郵件服務器,發送。
            當使用java開發的時候,我們常常使用JavaMail API 來連接郵件服務器發送郵件。但是這種方式過于笨重(主要由郵件的靈活性造成的),所以當你需要多次使用這種方式發送郵件的時候,最好寫一個wrapper.根據使用的方式不同,wrapper可以是發送某一特定的郵件,比如說密碼提醒,或者作為一種通用的模式,接受主題,接收人,郵件內容作為參數。
            一旦使用wrapper發送郵件,你需要一個自主構建消息的系統。讓我們使用密碼提醒作為例子。基本上所有的郵件都包含主題,內容和接收人。當我們發送密碼提醒郵件的時候,用戶地址和密碼是從某個記錄登陸信息的知識庫里提取的。主題和內容需要和數據庫提取的數據合并,并且被保存在某個地方。系統設計最大的問題就是在什么地方保存這種類型的字符串。在很多情形下,字串被保存在屬性文件里,這種方式分離了數據和源代碼,并且使本地化更加容易。我在很多web應用系統中使用了這種存儲機制,但很不幸的是,這種方式有很多缺陷。
            以下是利用屬性文件存儲郵件字串不合適的原因:
          ·屬性文件使用一種非常簡單的數據結構-名稱和值組合。當你需要很多值對應一個名稱的時候這種結構就不合適了。比如,一個郵件有4個接收人,3個抄送人,使用屬性文件很難解決這個問題。
          ·屬性文件的格式非常嚴格。名稱和值必須在同一行上,所以當你編輯文件的時候長字符串是很難處理的。比如,把一個郵件的所有內容放進屬性文件是一件多么痛苦的事情。如果你希望值的內容包括換行,你必須使用
            另一種選擇是使用XML作為郵件模板,這也是本篇文章所要討論的內容。XML為你構建模板提供了極大的靈活性,并且它不會有屬性文件所有的格式限制,因此這種方式很容易處理長字符串。XML主要弱勢就是它處理起來比屬性文件復雜。使用屬性文件的時候,裝載文件和裝載后訪問文件非常容易。而裝載XML文件和使用java提供的多個XML處理庫之一處理XML文件就需要更多的工作了。
          這篇文章和所附的代碼提供了一個通用的模板使你能夠使用XML文件創建模板并且發送郵件,希望由此能夠減輕這個過程的痛苦。在這個模板里,我將使用Jakarta 項目里的Commons Digester 包來處理XML,使用JavaMail API發送郵件。

          郵件模板
            讓我們來看看郵件模板的格式。模板是XML文件,它包含一個根元素和一系列根的子元素。根元素是<email>。必要的子元素是<subject>, <body>, 和 <from>。可選的子元素是 <to>, <cc>, 和 <bcc>。如果你使用過郵件系統,那么你可以推導出這些元素實際包含的內容。可選的元素有多個實例,所以你可以為每種類型的接收者指定多個地址。我待會會在描述消息處理的時候來解釋運行機制。以下是一個模板文件的例子。
          <email>
              <from>rafe@rafe.us</from>
              <to>someone@example.com</to>
              <cc>someoneelse@example.com</cc>
              <bcc>rafe@rafe.us</bcc>
              <subject>This is the subject</subject>
              <body>This is the body of an email message.</body>
          </email>

          可定制的模板
            屬性文件的一個有用的特性是你可以使用MessageFormat 類用動態傳入的值替代屬性文件里的被指定參數。比如說,如果你需要在屬性文件里指定errors,其中一個errors是file not found, 你可以這樣寫:
          file.not.found.error=Error, could not find file {0}.
          然后,在運行時刻,你這樣使用MessageFormat:
          ResourceBundle bundle = ResourceBundle.getBundle(
                  "MyProperties", currentLocale);

          Object[] arguments    = { "some_file.txt" };

          String newString      = MessageFormat.format(
                  bundle.getString("file.not.found.error"), arguments);
          最后,newString 將包含Error, could not find file some_file.txt.我在這個系統里加入了類似的靈活性。 可以格式化所有的字符串,所以你可以在郵件模版的subject 和body元素里內嵌在屬性文件使用的同樣的令牌。
            在某種情形下,你希望在發送郵件的時候插入個人化的信息。比如,你希望在郵件內容里或者訂單的內容里包含收件人的姓。本系統使用MessageFormat 來處理郵件模版的內容和主題,從而解決這個問題。處理內容和主題的時候只使用一個參數數組。這樣主題里可以包含令牌{0}, {2}, {3},  內容可以包含令牌{0}, {1}, {4} 。我之所以采用這種方式是因為在很多情形下主題和內容使用相同的參數,同時這種方式也簡化了傳遞給EmailSender所需要的參數。

          處理模版
            創建完模版,下一步所要做的就是處理它。我們知道,現在有很多的XML處理包可供選擇。Commons Digester是Jakarta的公共項目,最初是為了在Struts項目中快速方便的解析Struts的的配置文件而產生的。它提供了從XML文件里的元素到使用類似于XPath  語法的數據結構的映射。 好處在于為了從        XML文件里得到某個元素你不必用SAX一個節點一個節點的解析,也不必使用DOM處理樹狀數據結構。
            下面這個方法從XML文件里讀取數據,然后把數據拷貝到EmailTemplate對象中。

          public static EmailTemplate getEmailTemplate(InputStream aStream)
          {
              Digester digester = new Digester();
              digester.setValidating(false);

              digester.addObjectCreate("email", EmailTemplate.class);

              digester.addBeanPropertySetter("email/subject", "subject");
              digester.addBeanPropertySetter("email/body", "body");
              digester.addBeanPropertySetter("email/from", "from");
              digester.addCallMethod("email/to", "addTo", 0);
              digester.addCallMethod("email/cc", "addCc", 0);
              digester.addCallMethod("email/bcc", "addBcc", 0);

              try
              {
                  return (EmailTemplate)digester.parse(aStream);
              }
              catch (IOException e)
              {
                  logger.error("Error: ", e);
                  return null;
              }
              catch (SAXException e)
              {
                  logger.error("Error: ", e);
                  return null;
              }
          }

            讓我們來逐行研究這段代碼。Commons Digester工作的原理是由你來指定解析文件的一些規則。因為沒有規范郵件模版的DTD文件,所以在指定處理規則之前,我將validating flag設定為false。開始處理文件的時候,我實例化Digester對象然后調用方法建立數據映射規則。首先,我調用addObjectCreate()方法來建立創建EmailTemplate對象的規則。email是XML模版文件的根元素。因此模版文件和EmailTemplate 對象一一對應。
            我使用addBeanPropertySetter()來處理在模版文件中只出現一次的元素。這個方法有兩個參數,元素的路徑和要調用的賦值方法。在第一次調用的時候,我指定在文件中符合email/subject 模式的元素應該賦值給EmailTemplate 類的subject 。我們用 “/”來描速XML文件的內嵌關系。在這個例子中,符合subject模式的元素是email 子元素。為了提供更多的靈活性我們可以使用Wildcards。參考Commons Digester的JavaDoc 你可以了解詳細的模式的構成方式。
            使用賦值方法處理在模版文件中出現多次的元素是不可行的。我們使用addCallMethod()來處理這種情形,這個方法從元素中取值并且調用指定的方法。我使用這個方法有三個參數的版本,它們是:匹配的模式,調用的方法,調用方法所使用的參數數量。在例子的三種情形中第三個參數都是0,說明符合模式的元素是調用方法的唯一參數。在EmailTemplate類中我定義了三個方法:addTo(), addCc(),  addBcc(),這三個方法將模版文件中的收件人列表加入到模版類的收件人集合中。
            郵件元素的六種類型的子元素的規則都被指定好之后,我開始解析這個文件。在這個例子中, 我傳入getEmailTemplate 方法的輸入參數InputStream 。parse方法可以解析File,SAX InputSource, InputStream,  Reader, 目標文件的URI。我使用InputStream。 由調用這個方法的代碼取得XML文件并且把它轉化為InputStream 。為了讓這個方法更加通用,我可以用Object作為參數,并且在方法內部使用instanceof 來確定參數的類型,再用相應的方式來處理。
            方法parse 拋出IOException 或者SAXException。把這些異常傳給Log4J,由它來處理,返回null. 如果沒有異常拋出, 將返回由Digester創建的EmailTemplate對象。

          EmailTemplate類剩下的部分
            getEmailTemplate()方法是類EmailTemplate的核心。其他的部分是一些屬性值和一些輔助性的方法。有3個String 類型的屬性值:內容,主題,寄件人地址,3個ArrayList屬性值:to, CC, BCC 列表,這3個值都以String作為基本元素。還有相應的get,set和加入集合的方法。還有3個附加的方便的方法:getToAddresses(), getCcAddresses(), 和 getBccAddresses()。JavaMail接口需要InternetAddress 數組作為地址集合的參數,這些方法可以把對象的String數組轉化為JavaMail接口需要的數組形式。

          類EmailSender
            當模版文件被解析成EmailTemplate對象,下一步就是發送郵件信息。EmailSender 類包含一個靜態的,重載的方法-sendEmail()。 這個方法可以通過很多種方式調用,所有的方式都是對下面這個完全參數方法的一個引用:
          public static void sendEmail(
              String aTo,
              EmailTemplate aTemplate,
              String[] aArgs)
            參數不需要過多的解釋。第一個是郵件的發送地址。你可以在郵件模版里指定很多接收人地址,但是在運行時刻,大多數情況下,系統只需要一個接收人。比如說,你發送一封密碼提醒的郵件,只需要指定申請密碼的用戶的郵件地址。在郵件模版里指定的收件人列表在某種情況下適用:作為測試,系統需要發送郵件到特定收件人列表或者發送時需要包含特定收件人列表。比如說,假設一個系統每當訂單提交的時候需要通過一封郵件觸發一個workflow,在這種情形下郵件模版種特定的接收人地址是有意義的。
            第二個參數是EmailTemplate自身。第三個參數是MessageFormat解析郵件主題和內容所需要的參數集。由調用這個方法的代碼來創建個性化郵件模版所需要的信息數組。也有其他申明的方法簡化了這個方法的調用(所以你可以在不指定收件人,或者在沒有參數的情況下調用這個方法)。
            方法內部由使用JavaMail發送郵件所需要的一系列調用組成。我覺得使用JavaMail會造成許多冗余,我們來具體看一下。首先,我要通過檢測來確定EmailTemplate是否為空。如果為空,什么都不能做。設定的第一步是使用SMTP server的設置創建一個Properties對象(Hashtable)。我把SMTP server的設置設定在 文件里,所以我把這個值從屬性文件里讀出來然后放到我創建的properties對象里去。
            接著我創建了一個JavaMail Session 對象傳入Properties 對象。Session對象在創建MimeMessage對象的時候需要。這個是我待會要做的。然后我將From:的值指定到傳入參數EmailTemplate對象的相應欄位。下一步我把To:的值設定到我構建的消息中。這里會有一些技巧,因為用戶可以傳入To: 地址,同時郵件模版里也包含一些To:地址。問題在于JavaMail 喜歡使用數組描速地址列表,所以由我來決定接收人列表的有多大,然后構建傳入的參數。
            因為CC: BCC:的地址必須在模版里指定,我們可以直接來處理它們。我使用EmailTemplate類里的方法把其他的收件人加入到消息里。就像我開始提到的,我使用MessageFormat解析處理郵件主題和內容的方法所需要的參數集。做完之后,我把新的主題拷貝到消息主體里。如此處理消息的內容。剩下的就是調用Transport.send()并且傳入MimeMessage 對象。

          使用這個系統
            我剛才已經解釋了系統的運作原理,現在我來解釋如何通過 servlet來使用它,在其他程序里調用的方式是類似的。以下是代碼:
          // Grab the email template.
          InputStream template =
              getServlet()
                  .getServletConfig()
                  .getServletContext()
                  .getResourceAsStream(
                      "/WEB-INF/email/registrationNotification.xml");

          EmailTemplate notification = EmailTemplate.getEmailTemplate(template);

          // Create the section of the email containing the actual user data.
          String[] args = { "Rafe" };

          EmailSender.sendEmail("rafe@rafe.us", notification, args);
            使用這個系統的第一步是把你的XML模版文件轉化成InputStream。 因為我使用的是servlet,我從ServletContext取得這個文件。當然還有其他的方式取得這個文件,但是在servlet環境里,這種方式很好用。我只用把InputStream 傳給剛才所描述的EmailTemplate.getEmailTemplate()方法就可以了。下一步,建立個性化郵件所需要的參數數組,然后調用方法EmailSender.sendEmail()。
          更多
            這個系統還可以更多的優化,有兩個比較明顯的需要改善的地方:系統應該同時支持純文本和HTML;支持附件。創建這種類型的信息需要使用類型javax.mail.MimeMultipart。還有在何處存儲附件和如何指定附件的問題。在我的系統里,我沒有在模版文件里處理附件,因為我的附件是在郵件發送的時候創建的。


          Rafe Colburn 是一個Java開發工程師,同時也是一名計算機圖書的作者,他使用過Perl,CGI, HTML, JAVA
          posted on 2006-02-28 09:53 黑咖啡 閱讀(578) 評論(3)  編輯  收藏 所屬分類: tec

          FeedBack:
          # shuaige
          2006-07-20 10:58 | 文學臺
          每什么  回復  更多評論
            
          # re: Java中使用XML創建EMAIL模板
          2006-07-20 10:58 | 文學臺
          沒什么  回復  更多評論
            
          # re: Java中使用XML創建EMAIL模板
          2006-07-20 10:59 | 文學臺
          @文學臺
            回復  更多評論
            

          <2025年5月>
          27282930123
          45678910
          11121314151617
          18192021222324
          25262728293031
          1234567

          留言簿(2)

          隨筆分類(67)

          文章分類(43)

          Good Article

          Good Blogs

          Open Source

          最新隨筆

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 樟树市| 金寨县| 水城县| 岳阳县| 鄂托克前旗| 大洼县| 双桥区| 海安县| 丰县| 宜都市| 神农架林区| 宝丰县| 耒阳市| 乐至县| 通州市| 改则县| 平舆县| 灌云县| 饶平县| 辉南县| 杭锦旗| 绵竹市| 平谷区| 兴业县| 伊通| 平顶山市| 金昌市| 崇明县| 新田县| 枣阳市| 高州市| 盐边县| 石渠县| 泸西县| 鞍山市| 朔州市| 湾仔区| 荆州市| 昔阳县| 开阳县| 定远县|