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,一经查实,立即删除!

相关文章

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

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

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

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

在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;就把自己绕糊涂了。头晕脑胀地坚持…

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

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

在AWS Elastic MapReduce上运行PageRank Hadoop作业

在上一篇文章中&#xff0c;我描述了执行PageRank计算的示例&#xff0c;该示例是使用Apache Hadoop进行Mining Massive Dataset课程的一部分。 在那篇文章中&#xff0c;我接受了Java中现有的Hadoop作业&#xff0c;并做了一些修改&#xff08;添加了单元测试&#xff0c;并通…

java sort 第二个参数_详解java Collections.sort的两种用法

Collections是一个工具类&#xff0c;sort是其中的静态方法&#xff0c;是用来对List类型进行排序的&#xff0c;它有两种参数形式&#xff1a;public static > void sort(List list) {list.sort(null);}public static void sort(List list, Comparator super T> c) {lis…

MVC3 中使用Unity实现依赖注入

前言&#xff1a;前段时间一直在研究依赖注入&#xff0c;不过不是在MVC框架中使用&#xff0c;今天突然想到在MVC中使用Unity实现依赖注入&#xff0c;一时慌了&#xff0c;不知道从何下手&#xff0c;接着就是网上不停的找资料&#xff0c;下面我把我找到的资料分享下&#x…

ms2005 SQL Server设置改为SQL Server身份验证

1.为 SQL Server 2005 Express Edition 或 SQL Server 2005 Developer Edition 启用远程连接 必须为要从远程计算机连接到的每个 SQL Server 2005 实例启用远程连接。为此&#xff0c;请按照下列步骤操作&#xff1a; 1.单击“开始”&#xff0c;依次指向“程序”、“Microsoft…

在Graphite中存储Hystrix的几个月历史指标

Hystrix的杀手级功能之一是低延迟&#xff0c;数据密集型且美观的仪表板 &#xff1a; 即使这只是Hystrix实际操作的副作用&#xff08;断路器&#xff0c;线程池&#xff0c;超时等&#xff09;&#xff0c;它也往往是最令人印象深刻的功能。 为了使其工作&#xff0c;您必须…

NYOJ-----最少乘法次数

最少乘法次数 时间限制&#xff1a;1000 ms | 内存限制&#xff1a;65535 KB难度&#xff1a;3描述给你一个非零整数&#xff0c;让你求这个数的n次方&#xff0c;每次相乘的结果可以在后面使用&#xff0c;求至少需要多少次乘。如24&#xff1a;2*222&#xff08;第一次乘&a…

php投票系统中各个文件的作用说明,PHP开发简单投票系统之投票页面功能模块(二)...

当完成前面的投票后&#xff0c;可以选择点击查看结果查看每个项目的总票数和所有项目的投票百分比。点击“查看结果”后程序会自动计算每个项目的票数和所占百分比。使用了隐藏表单属性隐藏域在页面中对于用户是不可见的&#xff0c;在表单中插入隐藏域的目的在于收集或发送信…

ref 和out 关键字

ref 和out 关键字 通过对CLR的学习&#xff0c;我们可以知道&#xff0c;CLR默认所有方法参数都是传值的。对于引用类型的对象&#xff0c;传递的是对象的引用&#xff08;指向对象的指针&#xff09;&#xff0c;被调用者拥有该对象的引用的拷贝&#xff0c;能够修改对象&…

php-cli下载,php-cli-color

一个简单的 PHP 命令行 cli 输出彩色的类库安装composer require wujunze/php-cli-color ~1.0使用getColoredString("Testing Colors class, this is purple string on yellow background.", "purple", "yellow") . PHP_EOL;echo $colors->ge…

您会后悔对Lambdas应用重载!

编写好的API很难。 非常辛苦。 如果您想让用户喜欢您的API&#xff0c;则必须考虑很多事情。 您必须在以下两者之间找到适当的平衡&#xff1a; 有用性 易用性 向后兼容 前向兼容性 之前&#xff0c;在我们的文章&#xff1a; 如何设计良好的常规API中&#xff0c;我们已经…

如何:优化Hive ZooKeeper Lock Manager实施

Hive一直使用ZooKeeper作为分布式锁定管理器来支持HiveServer2中的并发。 基于ZooKeeper的锁管理器在小型环境中运行良好。 但是&#xff0c;随着越来越多的用户从HiveServer迁移到HiveServer2并开始创建大量并发会话&#xff0c;可能会出现问题。 主要问题是Hiveserver2和ZooK…

《图解HTTP》第1章 了解Web及网络基础

《图解HTTP》第1章 了解Web及网络基础 1. 使用 HTTP 协议访问 Web1.1 网络基础 TCP/IP1.2 TCP/IP 协议族1.2.1 TCP/IP 的分层管理 1. 使用 HTTP 协议访问 Web Web 使用一种名为 HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09; 的协议作为…

JBoss Forge NetBeans集成–入门

JBoss Forge是构建基于Maven的Java EE项目的最快方法。 因此&#xff0c;它已经具有了令人敬畏的功能&#xff0c;使您作为开发人员的生活更加轻松。 在大多数情况下&#xff0c;使用Forge的人们可能会对创建Web应用程序感兴趣。 有很多入门Forge基础的方法。 您可以查看quick…

SQL语法的重要知识点总结

好几年没写SQL语句了。现在到了新的team&#xff0c;需要用到数据库。作为QA的话时常需要使用客户端工具连接到数据库中找寻或修改数据。这么长时间没使用&#xff0c;一些SQL的使用技巧都忘得差不多了。晚上看了一些资料&#xff0c;花了1个多小时又捡起了曾经的知识。现在总结…

您需要了解的有关默认方法的所有信息

因此&#xff0c;默认方法是……昨天的新闻&#xff0c;对不对&#xff1f; 是的&#xff0c;但是使用一年后&#xff0c;积累了很多事实&#xff0c;我想将这些事实收集在一个地方&#xff0c;供刚开始使用它们的开发人员使用。 甚至有经验的人都可以找到他们不知道的一两个细…