php cdi_CDI和EJB:在事务成功时发送异步邮件
php cdi
再次問好! :)
這次,我選擇了一項常見任務,我認為大多數(shù)情況下都以錯誤的方式完成:發(fā)送電子郵件。 并非所有人都不知道電子郵件API的工作方式,例如JavaMail或Apache的commons-email 。 我通??吹降囊粋€問題是,它們低估了使發(fā)送郵件例程異步的需求,并且該例程也應該僅在基礎事務成功提交(大多數(shù)情況下)時運行。
想一想用戶在線購物的常見用例。 完成后,他可能希望接收訂單確認電子郵件。 下訂單的過程有點復雜:我們通常會在許多不同的表中插入記錄,也可能會刪除記錄以從庫存中刪除物品等。 當然,所有這些都必須在單個原子事務中完成:
//A sample EJB method //(using CMT for transaction management) public void saveOrder() {//saving some productsentityManager.persist(product1);entityManager.persist(product2);//removing them from stockentityManager.remove(product1);//and at last, we have to send that emailsendOrderConfirmationMail(); //the transaction has not yet been commited by this point }就像上面的偽代碼一樣,我們通常會努力將事務邏輯排除在代碼之外。 也就是說,我們使用CMT(容器管理的事務)來使容器為我們做所有事情,并使代碼更整潔。 我們的方法調(diào)用完成這樣的權利后 ,EJB容器提交我們的事務。 這是問題編號1:當調(diào)用sendOrderConfirmationMail()方法時,我們無法知道事務是否成功。 用戶可能會收到不存在的訂單的確認。
如果您尚未意識到這一點,則只需使用您的任何代碼進行測試。 在對我們的封閉方法調(diào)用結束之前,對EntityManager.persist()的那些調(diào)用不會觸發(fā)任何數(shù)據(jù)庫命令。 只需設置一個斷點,然后自己看看即可。 我已經(jīng)多次看到這樣的困惑。
因此,在發(fā)生回滾的情況下,我們無需發(fā)送任何電子郵件。 發(fā)生問題的原因有很多:系統(tǒng)故障,某些業(yè)務規(guī)則可能會拒絕購買,信用卡驗證等。
因此,我們已經(jīng)知道,使用CMT時,我們很難知道交易何時成功。 下一個問題是使郵件例程異步,完全獨立于我們的訂購例程。 想象一下,如果訂購過程一切正常,但是嘗試發(fā)送電子郵件時發(fā)生一些異常怎么辦? 我們是否應該僅因為無法發(fā)送確認郵件而回滾所有內(nèi)容? 我們是否應該僅僅因為我們的郵件服務器表現(xiàn)不佳而真的阻止用戶在我們的商店購買商品?
我知道這樣的業(yè)務需求可以任意選擇,但是請記住,通常希望使發(fā)送郵件的固有延遲不干擾訂單處理。 大多數(shù)時候,處理訂單是我們的主要目標。 諸如發(fā)送電子郵件之類的低優(yōu)先級任務甚至可以推遲到服務器負載較低的時候。
開始了
為了解決這個問題,我選擇了一種純Java EE方法。 無需使用第三方API。 我們的環(huán)境包括:
- JDK 7或更高版本。
 - Java EE 7(JBoss Wildfly 8.1.0)
 - CDI 1.1
 - EJB 3.2
 - JavaMail 1.5
 
我已經(jīng)建立了一個小型網(wǎng)絡項目,因此您可以看到所有工作, 如果需要 , 可以在此處下載 。
在深入研究代碼之前,請簡要觀察一下:下面顯示的解決方案主要包括CDI事件和EJB異步調(diào)用。 這是因為CDI 1.1規(guī)范不提供異步事件處理。 似乎仍在為CDI 2.0規(guī)范進行討論。 因此,純CDI方法可能會比較棘手。 我并不是說這是不可能的,我什至沒有嘗試過。
該代碼示例僅是一個“注冊客戶”用例的信條。 我們將在其中發(fā)送電子郵件以確認用戶注冊的位置。 總體架構如下所示:
該代碼示例還提供了一個“失敗測試用例”,因此您實際上可以看到,在進行回滾時沒有發(fā)送電子郵件。 我只是在這里向您展示“幸福的道路”,從托管Bean調(diào)用我們的CustomerService EJB開始。 沒什么有趣的,只是樣板:
在我們的CustomerService EJB內(nèi)部,事情開始變得有趣。 通過使用CDI API,我們可以在saveSuccess()方法的末尾觸發(fā)MailEvent事件:
@Stateless public class CustomerService {@Injectprivate EntityManager em;@Injectprivate Event<MailEvent> eventProducer;public void saveSuccess() {Customer c1 = new Customer();c1.setId(1L);c1.setName("John Doe");em.persist(c1);sendEmail();}private void sendEmail() {MailEvent event = new MailEvent();event.setTo("some.email@foo.com");event.setSubject("Async email testing");event.setMessage("Testing email");eventProducer.fire(event); //firing event!} }MailEvent類只是代表我們事件的常規(guī)POJO。 它封裝了有關電子郵件的信息:收件人,主題,文本消息等:
public class MailEvent {private String to; //recipient addressprivate String message;private String subject;//getters and setters }如果您是CDI的新手,并且對此事件仍然有些困惑, 請閱讀docs 。 它應該給您一個想法。
接下來是時候使用事件觀察器MailService EJB了。 這是一個簡單的EJB,帶有一些JavaMail魔術和一些應注意的注釋 :
@Singleton public class MailService {@Injectprivate Session mailSession; //more on this later@Asynchronous@Lock(LockType.READ)public void sendMail(@Observes(during = TransactionPhase.AFTER_SUCCESS) MailEvent event) {try {MimeMessage m = new MimeMessage(mailSession);Address[] to = new InternetAddress[] {new InternetAddress(event.getTo())};m.setRecipients(Message.RecipientType.TO, to);m.setSubject(event.getSubject());m.setSentDate(new java.util.Date());m.setContent(event.getMessage(),"text/plain");Transport.send(m);} catch (MessagingException e) {throw new RuntimeException(e);}} }就像我說的那樣,這只是一個常規(guī)的EJB。 使此類成為事件觀察者,更確切地說是sendMail()方法的原因是第9行中的@Observes批注。僅此批注將使此方法在事件觸發(fā)后運行。
但是,我們只需要在提交事務 !時才觸發(fā)此事件。 回滾不應觸發(fā)電子郵件。 這就是“ during”屬性的來源。通過指定值TransactionPhase.AFTER_SUCCESS,我們確保僅在事務成功提交后才觸發(fā)事件。
最后但并非最不重要的一點是,我們還需要使此邏輯與主邏輯在單獨的線程中運行。 它必須異步運行。 為此,我們僅使用了兩個EJB批注@Asynchronous和@Lock(LockType.READ) 。 后者@Lock(LockType.READ)不是必需的,但強烈建議使用。 它保證不使用鎖,并且多個線程可以同時使用該方法。
在JBoss Wildfly 8.1.0中配置郵件會話
作為獎勵,我將展示如何在JBoss WildFly中正確配置郵件“源”。 郵件源與數(shù)據(jù)源非常相似,除了它們用于發(fā)送電子郵件而不是用于數(shù)據(jù)庫:)。 這是一種使代碼與如何建立與郵件服務器的連接脫鉤的方法。 我使用了與我的Gmail帳戶的連接,但是您無需切換MailService類中的任何代碼即可切換到所需的任何內(nèi)容。
可以使用@Resource批注以其JNDI名稱檢索javax.mail.Session對象:
@Resource(mappedName = "java:jboss/mail/Gmail") private Session mailSession;您可能已經(jīng)注意到,在我以前的代碼片段中,我沒有使用@Resource批注,而僅使用了CDI的@Inject 。 好吧,如果您好奇我是怎么做到的,只需下載源代碼并看一下即可。 ( 提示:我使用了生產(chǎn)者幫助器類 。)
繼續(xù),只需打開standalone.xml (如果處于域模式,則打開domain.xml),然后首先查找“郵件子系統(tǒng)”。 它看起來應該像這樣:
<subsystem xmlns="urn:jboss:domain:mail:2.0"><mail-session name="default" jndi-name="java:jboss/mail/Default"><smtp-server outbound-socket-binding-ref="mail-smtp"/></mail-session> </subsystem>默認情況下,已經(jīng)在本地主機上運行了一個已提供的郵件會話。 由于您的開發(fā)機器上可能沒有運行任何郵件服務器,因此我們將添加一個指向gmail的新郵件服務器:
<subsystem xmlns="urn:jboss:domain:mail:2.0"><mail-session name="default" jndi-name="java:jboss/mail/Default"><smtp-server outbound-socket-binding-ref="mail-smtp"/></mail-session><mail-session name="gmail" jndi-name="java:jboss/mail/Gmail" from="your.account@gmail.com"><smtp-server outbound-socket-binding-ref="mail-gmail" ssl="true" username="your.account@gmail.com" password="your-password"/></mail-session> </subsystem>查看第5、6和7行如何突出顯示。 那是我們的新郵件會話。 但這還不是全部。 我們?nèi)匀恍枰獎?chuàng)建一個套接字綁定到我們的新郵件會話。 因此,在standalone.xml內(nèi)查找一個名為socket-binding-group的元素:
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}"><!-- a bunch of stuff here --><outbound-socket-binding name="mail-smtp"><remote-destination host="localhost" port="25"/></outbound-socket-binding></socket-binding-group>現(xiàn)在,通過創(chuàng)建新的outbound-socket-binding元素,將gmail端口添加到現(xiàn)有端口:
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}"><!-- a bunch of stuff here --><outbound-socket-binding name="mail-smtp"><remote-destination host="localhost" port="25"/></outbound-socket-binding><!-- "mail-gmail" is the same name we used in the mail-session config --><outbound-socket-binding name="mail-gmail"><remote-destination host="smtp.gmail.com" port="465"/></outbound-socket-binding></socket-binding-group>就是這個。 如果您有任何問題,請發(fā)表評論:)。 后來!
翻譯自: https://www.javacodegeeks.com/2015/03/cdi-ejb-sending-asynchronous-mail-on-transaction-success.html
php cdi
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結
以上是生活随笔為你收集整理的php cdi_CDI和EJB:在事务成功时发送异步邮件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: linux linux(linux 下j
 - 下一篇: viewpager默认界面_使用默认方法