php cdi_CDI和EJB:在事务成功时发送异步邮件
php cdi
再次問(wèn)好! :)
這次,我選擇了一項(xiàng)常見(jiàn)任務(wù),我認(rèn)為大多數(shù)情況下都以錯(cuò)誤的方式完成:發(fā)送電子郵件。 并非所有人都不知道電子郵件API的工作方式,例如JavaMail或Apache的commons-email 。 我通常看到的一個(gè)問(wèn)題是,它們低估了使發(fā)送郵件例程異步的需求,并且該例程也應(yīng)該僅在基礎(chǔ)事務(wù)成功提交(大多數(shù)情況下)時(shí)運(yùn)行。
想一想用戶(hù)在線購(gòu)物的常見(jiàn)用例。 完成后,他可能希望接收訂單確認(rèn)電子郵件。 下訂單的過(guò)程有點(diǎn)復(fù)雜:我們通常會(huì)在許多不同的表中插入記錄,也可能會(huì)刪除記錄以從庫(kù)存中刪除物品等。 當(dāng)然,所有這些都必須在單個(gè)原子事務(wù)中完成:
//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 }就像上面的偽代碼一樣,我們通常會(huì)努力將事務(wù)邏輯排除在代碼之外。 也就是說(shuō),我們使用CMT(容器管理的事務(wù))來(lái)使容器為我們做所有事情,并使代碼更整潔。 我們的方法調(diào)用完成這樣的權(quán)利后 ,EJB容器提交我們的事務(wù)。 這是問(wèn)題編號(hào)1:當(dāng)調(diào)用sendOrderConfirmationMail()方法時(shí),我們無(wú)法知道事務(wù)是否成功。 用戶(hù)可能會(huì)收到不存在的訂單的確認(rèn)。
如果您尚未意識(shí)到這一點(diǎn),則只需使用您的任何代碼進(jìn)行測(cè)試。 在對(duì)我們的封閉方法調(diào)用結(jié)束之前,對(duì)EntityManager.persist()的那些調(diào)用不會(huì)觸發(fā)任何數(shù)據(jù)庫(kù)命令。 只需設(shè)置一個(gè)斷點(diǎn),然后自己看看即可。 我已經(jīng)多次看到這樣的困惑。
因此,在發(fā)生回滾的情況下,我們無(wú)需發(fā)送任何電子郵件。 發(fā)生問(wèn)題的原因有很多:系統(tǒng)故障,某些業(yè)務(wù)規(guī)則可能會(huì)拒絕購(gòu)買(mǎi),信用卡驗(yàn)證等。
因此,我們已經(jīng)知道,使用CMT時(shí),我們很難知道交易何時(shí)成功。 下一個(gè)問(wèn)題是使郵件例程異步,完全獨(dú)立于我們的訂購(gòu)例程。 想象一下,如果訂購(gòu)過(guò)程一切正常,但是嘗試發(fā)送電子郵件時(shí)發(fā)生一些異常怎么辦? 我們是否應(yīng)該僅因?yàn)闊o(wú)法發(fā)送確認(rèn)郵件而回滾所有內(nèi)容? 我們是否應(yīng)該僅僅因?yàn)槲覀兊泥]件服務(wù)器表現(xiàn)不佳而真的阻止用戶(hù)在我們的商店購(gòu)買(mǎi)商品?
我知道這樣的業(yè)務(wù)需求可以任意選擇,但是請(qǐng)記住,通常希望使發(fā)送郵件的固有延遲不干擾訂單處理。 大多數(shù)時(shí)候,處理訂單是我們的主要目標(biāo)。 諸如發(fā)送電子郵件之類(lèi)的低優(yōu)先級(jí)任務(wù)甚至可以推遲到服務(wù)器負(fù)載較低的時(shí)候。
開(kāi)始了
為了解決這個(gè)問(wèn)題,我選擇了一種純Java EE方法。 無(wú)需使用第三方API。 我們的環(huán)境包括:
- JDK 7或更高版本。
- Java EE 7(JBoss Wildfly 8.1.0)
- CDI 1.1
- EJB 3.2
- JavaMail 1.5
我已經(jīng)建立了一個(gè)小型網(wǎng)絡(luò)項(xiàng)目,因此您可以看到所有工作, 如果需要 , 可以在此處下載 。
在深入研究代碼之前,請(qǐng)簡(jiǎn)要觀察一下:下面顯示的解決方案主要包括CDI事件和EJB異步調(diào)用。 這是因?yàn)镃DI 1.1規(guī)范不提供異步事件處理。 似乎仍在為CDI 2.0規(guī)范進(jìn)行討論。 因此,純CDI方法可能會(huì)比較棘手。 我并不是說(shuō)這是不可能的,我什至沒(méi)有嘗試過(guò)。
該代碼示例僅是一個(gè)“注冊(cè)客戶(hù)”用例的信條。 我們將在其中發(fā)送電子郵件以確認(rèn)用戶(hù)注冊(cè)的位置。 總體架構(gòu)如下所示:
該代碼示例還提供了一個(gè)“失敗測(cè)試用例”,因此您實(shí)際上可以看到,在進(jìn)行回滾時(shí)沒(méi)有發(fā)送電子郵件。 我只是在這里向您展示“幸福的道路”,從托管Bean調(diào)用我們的CustomerService EJB開(kāi)始。 沒(méi)什么有趣的,只是樣板:
在我們的CustomerService EJB內(nèi)部,事情開(kāi)始變得有趣。 通過(guò)使用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類(lèi)只是代表我們事件的常規(guī)POJO。 它封裝了有關(guān)電子郵件的信息:收件人,主題,文本消息等:
public class MailEvent {private String to; //recipient addressprivate String message;private String subject;//getters and setters }如果您是CDI的新手,并且對(duì)此事件仍然有些困惑, 請(qǐng)閱讀docs 。 它應(yīng)該給您一個(gè)想法。
接下來(lái)是時(shí)候使用事件觀察器MailService EJB了。 這是一個(gè)簡(jiǎn)單的EJB,帶有一些JavaMail魔術(shù)和一些應(yīng)注意的注釋 :
@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);}} }就像我說(shuō)的那樣,這只是一個(gè)常規(guī)的EJB。 使此類(lèi)成為事件觀察者,更確切地說(shuō)是sendMail()方法的原因是第9行中的@Observes批注。僅此批注將使此方法在事件觸發(fā)后運(yùn)行。
但是,我們只需要在提交事務(wù) !時(shí)才觸發(fā)此事件。 回滾不應(yīng)觸發(fā)電子郵件。 這就是“ during”屬性的來(lái)源。通過(guò)指定值TransactionPhase.AFTER_SUCCESS,我們確保僅在事務(wù)成功提交后才觸發(fā)事件。
最后但并非最不重要的一點(diǎn)是,我們還需要使此邏輯與主邏輯在單獨(dú)的線程中運(yùn)行。 它必須異步運(yùn)行。 為此,我們僅使用了兩個(gè)EJB批注@Asynchronous和@Lock(LockType.READ) 。 后者@Lock(LockType.READ)不是必需的,但強(qiáng)烈建議使用。 它保證不使用鎖,并且多個(gè)線程可以同時(shí)使用該方法。
在JBoss Wildfly 8.1.0中配置郵件會(huì)話
作為獎(jiǎng)勵(lì),我將展示如何在JBoss WildFly中正確配置郵件“源”。 郵件源與數(shù)據(jù)源非常相似,除了它們用于發(fā)送電子郵件而不是用于數(shù)據(jù)庫(kù):)。 這是一種使代碼與如何建立與郵件服務(wù)器的連接脫鉤的方法。 我使用了與我的Gmail帳戶(hù)的連接,但是您無(wú)需切換MailService類(lèi)中的任何代碼即可切換到所需的任何內(nèi)容。
可以使用@Resource批注以其JNDI名稱(chēng)檢索javax.mail.Session對(duì)象:
@Resource(mappedName = "java:jboss/mail/Gmail") private Session mailSession;您可能已經(jīng)注意到,在我以前的代碼片段中,我沒(méi)有使用@Resource批注,而僅使用了CDI的@Inject 。 好吧,如果您好奇我是怎么做到的,只需下載源代碼并看一下即可。 ( 提示:我使用了生產(chǎn)者幫助器類(lèi) 。)
繼續(xù),只需打開(kāi)standalone.xml (如果處于域模式,則打開(kāi)domain.xml),然后首先查找“郵件子系統(tǒng)”。 它看起來(lái)應(yī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>默認(rèn)情況下,已經(jīng)在本地主機(jī)上運(yùn)行了一個(gè)已提供的郵件會(huì)話。 由于您的開(kāi)發(fā)機(jī)器上可能沒(méi)有運(yùn)行任何郵件服務(wù)器,因此我們將添加一個(gè)指向gmail的新郵件服務(wù)器:
<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行如何突出顯示。 那是我們的新郵件會(huì)話。 但這還不是全部。 我們?nèi)匀恍枰獎(jiǎng)?chuàng)建一個(gè)套接字綁定到我們的新郵件會(huì)話。 因此,在standalone.xml內(nèi)查找一個(gè)名為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)在,通過(guò)創(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>就是這個(gè)。 如果您有任何問(wèn)題,請(qǐng)發(fā)表評(píng)論:)。 后來(lái)!
翻譯自: https://www.javacodegeeks.com/2015/03/cdi-ejb-sending-asynchronous-mail-on-transaction-success.html
php cdi
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的php cdi_CDI和EJB:在事务成功时发送异步邮件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux linux(linux 下j
- 下一篇: php cdi_使用Fabric8在CD