再一次问好! :)

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

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

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

    php cdi 再次问好! :) 这次,我选择了一项常见任务,我认为大多数情况下都以错误的方式完成:发送电子邮件. 并非所有人都不知道电子邮件API的工作方式,例如JavaMail或Apache的co ...

  2. shell脚本编写监控本机内存和硬盘剩余空间,剩余内存小于 500M、根分区剩余空间小于 1000M时,发送报警邮件给 root 管理员

    监控本机内存和硬盘剩余空间,剩余内存小于 500M.根分区剩余空间小于 1000M时,发送报警邮件给 root 管理员 # 创建shell脚本文件 vim free.sh #!/bin/bash di ...

  3. Java实现注册时发送激活邮件验证

    在很多网站注册的时候,为了验证用户信息的真实合法,往往需要验证用户所填邮件的准确性.形式为:用户注册时填写邮箱,注册完成后,网站会向用户所填邮箱发送一封激活邮件,用户点击激活邮件中的链接后,方可完成注 ...

  4. python生日祝福短信_python-定时发送生日邮件祝福

    前言:按照人事的想法,能不能帮助他们定时发送员工生日祝福邮件. 正好学到python ,打算练下手 直接附上代码 个人想法:第一步数据库建立一张员工信息表:用于记录员工的名字,生日,邮箱.对于表没有专 ...

  5. SpringBoot中整合Mail实现发送模板邮件

    场景 项目搭建专栏: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/column/info/35688 实现最简单的带标题以及文本内容的邮件发送: https:/ ...

  6. RabbitMQ事务和Confirm发送方消息确认——深入解读

    引言 根据前面的知识(深入了解RabbitMQ工作原理及简单使用.Rabbit的几种工作模式介绍与实践)我们知道,如果要保证消息的可靠性,需要对消息进行持久化处理,然而消息持久化除了需要代码的设置之外 ...

  7. Spark修炼之道(高级篇)——Spark源码阅读:第九节 Task执行成功时的结果处理...

    Task执行成功时的结果处理 在上一节中,给出了Task在Executor上的运行代码演示,我们知道代码的最终运行通过的是TaskRunner方法 class TaskRunner(execBacke ...

  8. go发送smtp邮件时的踩坑记录——auth login、x509: cannot validate certificate for错误

    最近在用go写一个小工具,一个小功能是用smtp发邮件,用公司内网的邮箱服务器实现踩了不少坑 想知道x509: cannot validate certificate for解决的直接看2.2.1,想 ...

  9. 有事务冲突时节点怎么加入MGR集群

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 文章目录 1. 问题场景描述 2. 如何修复 2.1 找出事务差异点 2.2 决定如何处理 3. 小结 个别节点可能存在事 ...

最新文章

  1. Functional Programming Contest - September'14
  2. Exchange Server 2013 规划系列之日志容量规划、数据库容量规划
  3. MySql数据库概念
  4. mysql 外键和事务_Mysql (五)事务和外键
  5. python email模块写邮件_python常用模块email----创建简单的文本邮件并发送
  6. 【Python 必会技巧】三元表达式(三目运算符)
  7. linux安装python3.6 setuptools_linux下安装Python3.6.1
  8. Docker学习六:综合实践
  9. 为什么在Android上的某些设备上使用相机意图捕获的图像会被旋转?
  10. 2018-12-13
  11. 常用数字及模拟视频接口
  12. Matlab之min()、max()函数(求最小、最大值)
  13. Linux那些事儿 之 我是PCI(1)PCI,我们来了
  14. 动态调度之记分牌算法
  15. 日记侠:你对微信关键词是如何理解的?
  16. 刘鹏教授在淮安市应急管理局作报告
  17. 生物统计学(biostatistics)学习笔记(四)统计推断(已知样本推总体)
  18. ENVI中的辐射校正
  19. 《C++游戏编程入门(第4版)》——1.10 问与答
  20. 工业智能网关BL110应用之30:实现三菱 PLC FX3U 接入亚马逊云平台

热门文章

  1. 使用Servlet上传多张图片——实体层(ProductInfo.java)
  2. 要么干,要么滚,千万别混
  3. 微信消息提醒与消息数字提示之BadgeView
  4. ddm模型公式_简单判断目前行情——从股利贴现模型切入
  5. org.springframework.amqp.AmqpConnectException java.net.ConnectException的解决办法
  6. neo4j 迁移_在Kubernetes中迁移Neo4j图模式
  7. java文件端点续传效果图_Java单依赖性Dockerized HTTP端点
  8. 子类重写父类变量_为什么在子类中不重写超类的实例变量
  9. netflix 模式创新_创新设计模式:单例模式
  10. 私有方法与静态私有方法_每个私有静态方法都是新类的候选人