CDI和EJB:在事务成功时发送异步邮件

再一次问好! :)

这次,我选择了一项常见任务,我认为大多数情况下都以错误的方式完成:发送电子邮件。 并非所有人都不知道电子邮件API的工作方式,例如JavaMail或Apache的commons-email 。 我通常看到的一个问题是,它们低估了使发送邮件例程异步的需求,并且它也应该仅在基础事务成功提交(大多数情况下)时运行。

想一想用户在线购物的常见用例。 完成后,他可能想要接收订单确认电子邮件。 下订单的过程非常复杂:我们通常会在许多不同的表中插入记录,也可能会删除记录以从库存中删除物品等。 当然,所有这些都必须在单个原子事务中完成:

//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(容器管理的事务)来使容器为我们做所有事情,并使代码更整洁。 我们的方法调用完成这样的权利后 ,EJB容器提交我们的事务。 这是问题编号1:在调用sendOrderConfirmationMail()方法时,我们无法知道事务是否成功。 用户可能会收到不存在的订单的确认。

如果您尚未意识到这一点,则只需使用您的任何代码进行测试。 在我们的封闭方法调用结束之前,对EntityManager.persist()的那些调用不会触发任何数据库命令。 只需设置一个断点,然后自己看看。 我已经多次看到这样的困惑。

因此,如果发生回滚,我们不需要发送任何电子邮件。 发生问题的原因有很多:系统故障,某些业务规则可能会拒绝购买,信用卡验证等。

因此,我们已经知道,使用CMT时,我们很难知道交易何时成功。 下一个问题是使邮件例程异步,完全独立于我们的订购例程。 想象一下,如果在订购过程中一切正常,但尝试发送电子邮件时发生异常,该怎么办? 我们是否应该仅因为无法发送确认邮件而回滚所有内容? 我们是否应该仅仅因为我们的邮件服务器表现不佳而真的阻止用户在我们的商店购物?

我知道这样的业务需求可以任意选择,但是请记住,通常希望使发送邮件的固有延迟不干扰订单处理。 在大多数情况下,处理订单是我们的主要目标。 低优先级的任务(例如发送电子邮件)甚至可以推迟到服务器负载较低的时候。

开始了

为了解决这个问题,我选择了一种纯Java EE方法。 无需使用第三方API。 我们的环境包括:

  • JDK 7或更高版本。
  • Java EE 7(JBoss Wildfly 8.1.0)
  • CDI 1.1
  • EJB 3.2
  • JavaMail 1.5

我已经建立了一个小型网络项目,因此您可以看到所有工作, 如果需要 , 可以在此处下载 。

在深入研究代码之前,请简要观察一下:下面显示的解决方案主要包含CDI事件和EJB异步调用。 这是因为CDI 1.1规范不提供异步事件处理。 似乎仍在为CDI 2.0规范进行讨论。 因此,纯CDI方法可能会比较棘手。 我并不是说这是不可能的,我什至没有尝试过。

该代码示例仅是一个“注册客户”用例的信条。 我们将在其中发送电子邮件以确认用户注册的位置。 总体架构如下所示:

概述架构

该代码示例还提供了一个“失败测试用例”,因此您实际上可以看到在进行回滚时没有发送电子邮件。 我只是在这里向您展示“幸福的道路”,从受管Bean调用我们的CustomerService EJB开始。 没什么有趣的,只是样板:

托管豆

在我们的CustomerService EJB内部,事情开始变得有趣。 通过使用CDI API,我们可以在saveSuccess()方法末尾触发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类只是代表我们事件的常规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);}}
}

就像我说的那样,这只是一个普通的EJB。 使此类成为事件观察者,更确切地说是sendMail()方法的原因,是第9行中的@Observes注释。仅此注释将使该方法在事件触发后运行。

但是,我们需要仅在提交事务 !时才触发此事件。 回滚不应触发电子邮件。 这就是“ during”属性的来源。通过指定值TransactionPhase.AFTER_SUCCESS,我们确保仅在事务成功提交后才触发事件。

最后但并非最不重要的一点是,我们还需要使此逻辑与主逻辑在单独的线程中运行。 它必须异步运行。 为此,我们仅使用了两个EJB批注@Asynchronous@Lock(LockType.READ) 。 后者@Lock(LockType.READ)不是必需的,但强烈建议使用。 它保证不使用锁,并且多个线程可以同时使用该方法。

在JBoss Wildfly 8.1.0中配置邮件会话

作为奖励,我将展示如何在JBoss WildFly中正确配置邮件“源”。 邮件源与数据源非常相似,除了它们用于发送电子邮件而不是数据库内容:)。 这是一种使代码与如何建立与邮件服务器的连接脱钩的方法。 我使用了与我的Gmail帐户的连接,但是您无需切换MailService类中的任何代码即可切换到所需的任何内容。

可以使用@Resource批注以其JNDI名称检索javax.mail.Session对象:

@Resource(mappedName = "java:jboss/mail/Gmail")
private Session mailSession;

您可能已经注意到,在之前的代码片段中,我没有使用@Resource批注,而仅使用了CDI的@Inject 。 好吧,如果您好奇我是如何做到的,只需下载源代码并看一下即可。 ( 提示:我使用了生产者帮助器类 。)

继续,只需打开standalone.xml (如果处于域模式,则打开domain.xml),然后首先查找“邮件子系统”。 它看起来应该像这样:

<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>

默认情况下,已经在本地主机上运行了一个已提供的邮件会话。 由于您的开发机器上可能没有运行任何邮件服务器,因此我们将添加一个指向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行如何突出显示。 那是我们的新邮件会话。 但这还不是全部。 我们仍然需要创建一个套接字绑定到我们的新邮件会话。 因此,在standalone.xml内查找一个名为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>

现在,通过创建新的outbound-socket-binding元素,将gmail端口添加到现有端口:

<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>

就是这个。 如果您有任何问题,请发表评论:)。 后来!

翻译自: https://www.javacodegeeks.com/2015/03/cdi-ejb-sending-asynchronous-mail-on-transaction-success.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/360588.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SPFILE 、PFILE 的全面解读

这里先阐述一下数据库的启动过程&#xff1a; 1. 启动实例/例程&#xff08;nomount状态&#xff09;时&#xff0c;读取参数文件(文本文件PFILE 或服务器参数文件SPFILE)&#xff0c;分配SGA、启动后台进程、打开告警文件及后台进程跟踪文件&#xff1b; 2. 装载数据…

JAVA确定这天是这年的某一天_[Java] 练习题014: 输入某年某月某日,判断这一天是这一年的第几天?...

import java.util.*;public class Test014{public static void main(String[] args) throws Exception{int y,m,d;int sum0;int feb28;Scanner in new Scanner(System.in);System.out.print("请输入年份:");y in.nextInt();System.out.print("请输入月份:&quo…

使用默认方法的界面演变–第一部分:方法

几周前&#xff0c;我们详细研究了默认方法 -Java 8中引入的一项功能&#xff0c;该功能允许为接口方法提供实现&#xff0c;即方法主体&#xff0c;从而定义接口中的行为。 引入此功能是为了实现接口演进 。 在JDK的上下文中&#xff0c;这意味着在不破坏所有代码的情况下向接…

java左手握右手_环保型燃料—丙烷(C3H8)曾用于北京奥运会“祥云”火炬燃料,下列有关烷烃的说法不正确的是()A.丙烷分子中三个...

参考答案如下环保会祥【单选题】环境景观设计的核心就是以研究()为基础。型燃下列【判断题】构成中央处理器的两大部件是运算器和存储器。料丙料【单选题】通过下列哪种方式可以获得最强的免疫效果【单选题】根据断面图的标注形式&#xff0c;烷C烷烃下列绘制规范的断面图是【单…

windbg script ---- 禁用IsDebuggerPresent

简单的script r $t0 kernelBase!IsDebuggerPresent; eb $t00x9 31 c0 90 90强制把原代码改成xor eax, eax; nop; nop 注意在xp下&#xff0c;使用kernel32 转载于:https://www.cnblogs.com/hgy413/p/3693400.html

java两个和三个_Java语言基础(day_03)

数据类型中补充的几个小问题1)在定义Long或者Float类型变量的时候&#xff0c;要加L或者f。整数默认是int类型&#xff0c;浮点数默认晨double。byte&#xff0c;short在定义的肘候&#xff0c;他们接收的某实是一个int类型的值。这个是自己做了一个数据检测的&#xff0c;如果…

Hibernate中的JPA 2.1条件删除/更新和临时表

从JPA 2.0版开始&#xff0c; EntityManager提供了方法getCriteriaBuilder()来动态构建选择查询&#xff0c;而无需使用Java持久性查询语言&#xff08;JPQL&#xff09;进行字符串连接。 在2.1版中&#xff0c;此CriteriaBuilder提供了两个新方法createCriteriaDelete()和crea…

使用Hamcrest增强JUnit的测试能力

package com.jadyer.service;import java.util.HashMap; import java.util.Map;import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test;/*** 使用Hamcrest增强JUnit的测试能力* see Hamcrest框架提供了一些相对通俗并高效的方法来进行一些junit比较困…

在Websphere 8.0上安装Liferay 6.2 Enterprise Edition

为Liferay准备Websphere 当应用服务器二进制文件均已安装完毕&#xff0c;启动WebSphere应用服务器&#xff08;WAS&#xff09; 配置文件管理工具来创建一个配置文件适用于Liferay的和遵循的指示说明这里正式Liferay的文档。 这些说明用于在Websphere 8.5上安装Liferay 6.2&am…

java完数流程图_编程基本功训练:流程图画法及练习

对于“程序设计”的工作&#xff0c;许多初学者的理解就是“写代码”。同样&#xff0c;新手们苦恼的问题是&#xff0c;他们只会“写代码”。当接到一个新的任务&#xff0c;不少人总是在第一时间就爬到键盘上去敲代码。敲着敲着&#xff0c;就把自己绕糊涂了。头晕脑胀地坚持…

iOS modal view的关闭和显示问题

今天遇到一个问题&#xff1a; 需要关闭当前modal view&#xff0c;再present另一个modal view。 如果这么写&#xff1a; [self dismissModalViewControllerAnimated:NO];[self.delegate showBorderDetectionView];执行了第一句&#xff0c;再执行第二句的时候&#xff0c;sel…

西工大java高级网络编程_奥鹏西工大16春《JAVA高级网络编程》平时作业

西工大16春《JV高级网络编程》平时作业一、单选题(共 25 道试题&#xff0c;共 100 分。)1. 假设以tomt为w服务器&#xff0c;在hllopp应用中有一个hllo.jsp&#xff0c;它的文件路径如下: %TOMT_HOM%/wpps/hllopp/hllo/hllo.jsp 那么在浏览器端访问hllo.jsp的URL是()。. http:…

c#导出Excel

问题&#xff1a;用户查询一些数据&#xff0c;需要对页面上的数据生成excel文件。 分析&#xff1a;写文件&#xff0c;用excel进程&#xff0c;或者使用response方法&#xff08;都是通过网上查资料&#xff0c;个人就理解成这两个&#xff09; 使用excel进程有一个确定&…

使用Java 8流遍历递归数据结构

Streams API是Java 8中的真正瑰宝&#xff0c;我一直在为它们寻找或多或少的意外用途。 我最近写过有关将它们用作ForkJoinPool门面的文章 。 这是另一个有趣的例子&#xff1a;遍历递归数据结构。 事不宜迟&#xff0c;请看一下代码&#xff1a; class Tree {private int va…

mysql把用户权限授予新用户_MySQL新建普通用户和库并授予新用户对新库的所有权限...

新建库新建cmf和amon两个库&#xff0c;并指定默认字符集mysql> create database cmf DEFAULT CHARACTER SET utf8;Query OK, 1 row affected (0.00 sec)mysql> create database amon DEFAULT CHARACTER SET utf8;Query OK, 1 row affected (0.00 sec)新建用户新建cmf和a…

快速入门:OpenShift上的Spring Boot和WildfFly 8.2

与Spring Boot&#xff0c;WildFly和OpenShift相比&#xff0c;这是真正的“快速入门”&#xff0c;与我上一篇更具描述性的文章相反。 先决条件 在开始构建应用程序之前&#xff0c;我们需要安装一个OpenShift免费帐户和客户端工具。 步骤1&#xff1a;建立WildFly应用程式 …

java开发环境搭建 pdf_01搭建java web开发环境.pdf

01搭建java web开发环境.pdf还剩19页未读&#xff0c;继续阅读下载文档到电脑&#xff0c;马上远离加班熬夜&#xff01;亲&#xff0c;很抱歉&#xff0c;此页已超出免费预览范围啦&#xff01;如果喜欢就下载吧&#xff0c;价低环保&#xff01;内容要点&#xff1a;( 7)在 M…

linux培训笔记1

第五章 文件和目录的管理 linux命令的基本格式 #命令 [选项] [参数] 1、linux下的常用命令 (1)ls 查看(列出)目录下的内容 -l 查看文件详细信息 drwxr-xr-x. 23 root root 4096 5月 31 15:06 var 文件类型和…

Java 8 Optional不仅用于替换空值

总览 在Java 8中&#xff0c;您可以返回Optional而不是返回null。 就像您在Java 7中所做的那样。这取决于您是否倾向于忘记检查null还是使用静态代码分析检查对nullalbe的引用&#xff0c;这可能有很大的不同。 但是&#xff0c;还有一种更引人注目的情况是将Optional视为具有…

WCF学习笔记之序列化

DataContractAttribute 与 DataMenberAttribute DataContractAttribute该特性只能用于枚举、类和结构体&#xff0c;而不能用于接口&#xff1b;又因为DataContractAttribute是不可以被继承的&#xff0c;所以要所有作为数据契约的类型自身必须有这么一个特性&#xff1b;而且一…