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

php cdi

再次问好! :)

这次,我选择了一项常见任务,我认为大多数情况下都以错误的方式完成:发送电子邮件。 并非所有人都不知道电子邮件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

php cdi

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

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

相关文章

linux中多进程调试,linux下用gdb调试多进程

今天来学习一下linux下gdb如何调试多进程&#xff0c;在学习之前我我们能先看一张表&#xff1a;这张表是gdb调试的命令表&#xff0c;这对那些对gdb不熟的同学来说是非常有必要的。一、多进程调试的命令1、set follow-fork-mode parent|child因为gdb在一般情况下&#xff0c;只…

初学者宝典:C语言入门基础知识大全(下)

06类型的自动转换和强制转换当同一表达式中各数据的类型不同时&#xff0c;编译程序会自动把它们转变成同一类型后再进行计算。转换优先级为&#xff1a;char < int < float < double 即左边级别“低“的类型向右边转换。具体地说&#xff0c;若在表达式中优先级最高的…

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

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

C语言中scanf函数的3种常见问题与应对技巧

在写代码时难免对一些知识点不熟悉&#xff0c;导致犯错&#xff0c;今天分享几点小知识给大家。空白符问题#includeint main(void){int a;printf("input the data ");scanf("%d ",&a); //这里多了一个回车符printf("%d",a);return 0;}结果…

aws上部署hadoop_在AWS Elastic MapReduce上运行PageRank Hadoop作业

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

在嵌套使用if语句时,C语言规定else总是什么?

C语言的语法规定&#xff1a;else子句总是与前面最近的不带else的if相结合&#xff0c;与书写格式无关。在C语言中&#xff0c;使用if和else关键字对条件进行判断。请先看下面的代码&#xff1a;#include int main(){ int age; printf("请输入你的年龄&#xff1a;&…

continue语句的作用是结束整个循环的执行吗?

continue 语句的作用是结束本次循环&#xff0c;跳过循环体中剩余的语句而强制进入下一次循环&#xff08;回到循环体的开头准备再次执行循环体&#xff09;。continue语句只用在 while、for 循环中&#xff0c;常与 if 条件语句一起使用&#xff0c;判断条件是否成立。使用方式…

linux 远程权限不够,Eclipse连接远程Hadoop集群开发时权限不足问题解决方案

eclipse连接远程Hadoop集群开发时报错Exception in thread "main" org.apache.hadoop.security.AccessControlException: Permission denied: userd, accessWRITE, inode"data":zxg:supergroup:rwxr-xr-xat org.apache.hadoop.hdfs.server.namenode.FSPerm…

一文掌握 C 智能指针的使用

RAII 与引用计数了解 objective-C/Swift 的程序员应该知道引用计数的概念。引用计数这种计数是为了防止内存泄露而产生的。基本想法是对于动态分配的对象&#xff0c;进行引用计数&#xff0c;每当增加一次对同一个对象的引用&#xff0c;那么引用对象的引用计数就会增加一次&a…

fwrite函数的一般调用形式是什么?

fwrite() 是C 语言标准库中的一个文件处理函数&#xff0c;功能是向指定的文件中写入若干数据块&#xff0c;如成功执行则返回实际写入的数据块数目。该函数以二进制形式对文件进行操作&#xff0c;不局限于文本文件。语法&#xff1a;fwrite(buffer,size,count,fp)参数&#x…

lambdas 排序_Java8 Lambdas:解释性能缺陷的排序

lambdas 排序与Peter Lawrey合作撰写 。 几天前&#xff0c;我对使用新的Java8声明式的排序性能提出了一个严重的问题。 在这里查看博客文章。 在那篇文章中&#xff0c;我仅指出了问题&#xff0c;但在这篇文章中&#xff0c;我将更深入地了解和解释问题的原因。 这将通过使用…

strncmp函数用法是什么

strncmp函数用法&#xff1a;函数原型int strcmp(char *str1,char * str2&#xff0c;int n)功能比较字符串str1和str2的前n个字符。头文件#include 返回值返回值&#xff1a;返回整数值&#xff1a;当str1<str2时&#xff0c;返回值<0&#xff1b; str1"str2时&…

分享10个适合初学者学习的C开源项目代码

1.WebbenchWebbench 是一个在 linux 下使用的非常简单的网站压测工具。它使用 fork ()模拟多个客户端同时访问我们设定的 URL&#xff0c;测试网站在压力下工作的性能&#xff0c;最多可以模拟 3 万个并发连接去测试网站的负载能力。Webbench 使用C语言编写&#xff0c; 代码实…

tomcat与tomee_Apache TomEE(和Tomcat)的自签名证书

tomcat与tomee可能在大多数Java EE项目中&#xff0c;您将拥有具有SSL支持&#xff08; https &#xff09;的部分或整个系统&#xff0c;因此浏览器和服务器可以通过安全连接进行通信。 这意味着在处理数据之前&#xff0c;已发送的数据已加密&#xff0c;传输并最终解密。 …

C 线程的使用~(上)

C 11 之前&#xff0c;C 语言没有对并发编程提供语言级别的支持&#xff0c;这使得我们在编写可移植的并发程序时&#xff0c;存在诸多的不便。现在 C 11 中增加了线程以及线程相关的类&#xff0c;很方便地支持了并发编程&#xff0c;使得编写的多线程程序的可移植性得到了很大…

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

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

C语言中的“三字母词”坑了工程师

某软件工程师接盘了前同事的项目&#xff0c;进度一拖再拖&#xff0c;最后发现问题出现在如下代码&#xff1a;// 注释语句 ??/2a b c;请注意代码中的“??/”&#xff0c;就是这注释隐藏的很深&#xff0c;让项目一拖再拖。"??/"会被编译器当作 /&#xff0c…

C 线程的使用~(下)

2.3 detach()detach() 函数的作用是进行线程分离&#xff0c;分离主线程和创建出的子线程。在线程分离之后&#xff0c;主线程退出也会一并销毁创建出的所有子线程&#xff0c;在主线程退出之前&#xff0c;它可以脱离主线程继续独立的运行&#xff0c;任务执行完毕之后&#x…

用c语言编写爱心的代码是什么

用c语言编写爱心的代码&#xff1a;输入完整代码如下&#xff1a;#include int main(void){float a,x,y;for(y1.5f; y>-1.5f; y-0.1f){for(x-1.5f; x<1.5f; x 0.05f){a x*x y*y-1;char ch a*a*a-x*x*y*y*y<0.0f?*: ; putchar(ch); }printf("\n");}retur…

c++ lambda 重载_您会后悔对Lambdas应用重载!

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