在Spring中使用异步事件实现同步事务
结合Scala+Spring,我们将采取一个很简单的场景:下订单,然后发送一封电子邮件。
编制一个服务:
@Service
class OrderService @Autowired() (orderDao: OrderDao, mailNotifier: OrderMailNotifier) {
@Transactional
def placeOrder(order: Order) {
orderDao save order //保存订单
mailNotifier sendMail order //发送邮件
}
}
上面代码是在保存订单和发送邮件两个同步执行,发送邮件需要连接邮件服务器,比较耗时,拖延了整个性能,我们采取异步发送电子邮件,利用Spring内置的自定义事件,与JMS或其他生产者 - 消费者类似。
case class OrderPlacedEvent(order: Order) extends ApplicationEvent
@Service
class OrderService @Autowired() (orderDao: OrderDao, eventPublisher: ApplicationEventPublisher) {
@Transactional
def placeOrder(order: Order) {
orderDao save order
eventPublisher publishEvent OrderPlacedEvent(order)
}
}
区别是继承了ApplicationEvent 之前是直接用 OrderMailNotifier 直接发送,而现在我们使用ApplicationEventPublisher 发送发邮件事件了。
事件监听者代码如下:
@Service
class OrderMailNotifier extends ApplicationListener[OrderPlacedEvent] {
def onApplicationEvent(event: OrderPlacedEvent) {
//sending e-mail...
}
}
在监听者方法中真正实现邮件发送。
但是Spring的ApplicationEvents是同步事件,意味着我们并没有真正实现异步,程序还会在这里堵塞,如果希望异步,我们需要重新定义一个ApplicationEventMulticaster,实现类型SimpleApplicationEventMulticaster和TaskExecutor:
@Bean
def applicationEventMulticaster() = {
val multicaster = new SimpleApplicationEventMulticaster()
multicaster.setTaskExecutor(taskExecutor())
multicaster
}
@Bean
def taskExecutor() = {
val pool = new ThreadPoolTaskExecutor()
pool.setMaxPoolSize(10)
pool.setCorePoolSize(10)
pool.setThreadNamePrefix("Spring-Async-")
pool
}
Spring通过使用TaskExecutor已经支持广播事件了,对onApplicationEvent() 标注 @Async
@Async
def onApplicationEvent(event: OrderPlacedEvent) { //...
如果你希望使用@Async,可以编制自己的异步执行器:
@Configuration
@EnableAsync
class ThreadingConfig extends AsyncConfigurer {
def getAsyncExecutor = taskExecutor()
@Bean
def taskExecutor() = {
val pool = new ThreadPoolTaskExecutor()
pool.setMaxPoolSize(10)
pool.setCorePoolSize(10)
pool.setThreadNamePrefix("Spring-Async-")
pool
}
}
@ EnableAsync是足够了。,默认情况下,Spring使用SimpleAsyncTaskExecutor类创建新的线程。
以上所有设置暴露一个真正的问题。现在,我们虽然使用其他线程发送一个异步消息处理。不幸的是,我们引入竞争条件。
- 开始事务
- 存储order到数据库
- 发送一个包装order的消息
- 确认
异步线程获得OrderPlacedEvent并开始处理。现在的问题是,它发生(3)之后,还是(4)之前或者(4)之后?这有一个很大的区别!在前者的情况下,交易也尚未提交订单所以不存在于数据库中。另一方面,延迟加载可能已经在工作,致使订单对象仍然然绑定在 PersistenceContext(缺省我们使用JPA)。
解决办法是使用 TransactionSynchronizationManager.,可以注册很多监听者 TransactionSynchronization,它对于事务的提交或回滚都有事件发送。
@Transactional
def placeOrder(order: Order) {
orderDao save order
afterCommit {
eventPublisher publishEvent OrderPlacedEvent(order)
}
}
private def afterCommit[T](fun: => T) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter {
override def afterCommit() {
fun
}
})
}
当前事务提交后 afterCommit()接受调用,可以安全地调用registerSynchronization()多次 - 监听器存储在Set并且本地保存到当前事务中,事务提交后消失。
我们将afterCommit方法单独抽象成一个类,分离关注。
class TransactionAwareApplicationEventPublisher(delegate: ApplicationEventPublisher)
extends ApplicationEventPublisher {
override def publishEvent(event: ApplicationEvent) {
if (TransactionSynchronizationManager.isActualTransactionActive) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter {
override def afterCommit() {
delegate publishEvent event
}
})
}
else
delegate publishEvent event
}
}
TransactionAwareApplicationEventPublisher是实现Spring的ApplicationEventPublisher。
我们要将这个新的实现告诉Spring替换掉旧的,用@Primary:
@Resource
val applicationContext: ApplicationContext = null
@Bean
@Primary
def transactionAwareApplicationEventPublisher() =
new TransactionAwareApplicationEventPublisher(applicationContext)
再看看原来的订单服务:
@Service
class OrderService @Autowired() (orderDao: OrderDao, eventPublisher: ApplicationEventPublisher) {
@Transactional
def placeOrder(order: Order) {
orderDao save order
eventPublisher publishEvent OrderPlacedEvent(order)
}
注意这里ApplicationEventPublisher已经是我们自己实现的TransactionAwareApplicationEventPublisher,将被自动注入这个服务。
最后,要在真正订单保存的业务代码上放置事务:
def placeOrder(order: Order) {
storeOrder(order)
eventPublisher publishEvent OrderPlacedEvent(order)
}
@Transactional
def storeOrder(order: Order) = orderDao save order
当然这没有根本解决问题,如果placeOrder有一个更大的事务怎么办?
在Spring中使用异步事件实现同步事务的更多相关文章
- spring中的异步事件
这里讲解一下Spring对异步事件机制的支持,实现方式有两种: 1.全局异步 即只要是触发事件都是以异步执行,具体配置(spring-config-register.xml)如下: Java代码 ...
- Spring中ApplicationContext对事件的支持
Spring中ApplicationContext对事件的支持 ApplicationContext具有发布事件的能力.这是因为该接口继承了ApplicationEventPublisher接口. ...
- spring中自定义Event事件的使用和浅析
在我目前接触的项目中,用到了许多spring相关的技术,框架层面的spring.spring mvc就不说了,细节上的功能也用了不少,如schedule定时任务.Filter过滤器. intercep ...
- FPGA设计中的异步复位、同步释放思想
1.一个简单的异步复位例子: module test( input clk, input rst_n, input data_in, output reg out ); always@(posedge ...
- Spring学习之Spring中AOP方式切入声明式事务
mybatis-spring官方文档说明 一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中.而不是给 MyBatis 创建一个新的 ...
- Spring中实现自定义事件
原理: 通过扩展ApplicationEvent,创建一个事件类CustomEvent.这个类必须定义一个默认的构造函数,它应该从ApplicationEvent类中继承的构造函数. 一旦定义事件类, ...
- Spring中的ApplicationContext事件机制
ApplicationContext的事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListerner接口来实现. 1. 创建EmailEvent pu ...
- Spring中基于XML的声明式事务控制配置步骤
1.配置事务管理器 2.配置事务的通知 此时,我们就需要导入事务的约束 tx名称空间和约束,同时也需要aop的 使用tx:advice标签配置事务通知 属性: id:给事务通知起一个唯一标识 tran ...
- Spring 中的事件机制
说到事件机制,可能脑海中最先浮现的就是日常使用的各种 listener,listener去监听事件源,如果被监听的事件有变化就会通知listener,从而针对变化做相应的动作.这些listener是怎 ...
随机推荐
- 如何实现数字lcd显示效果(原创)
如题,我最先想到的是找一种字体,然后来显示lcd的效果,但是字体又无法满足有空位的时候那个暗灰色的文字的效果,如下所示 就是前三位那些灰色的888,因为你设置数值的时候只能是从0-9的数字,而这灰色的 ...
- Bitmap基本概念及在Android4.4系统上使用BitmapFactory的注意事项
本文首先总结一下Bitmap的相关概念,然后通过一个实际的问题来分析设置BitmapFactory.options的注意事项,以减少不必要的内存占用率,避免发生OOM. 一. Bitmap的使用tri ...
- [AngularJS 2 实践 一]My First Angular App
最近一直在看关于AngularJS 2的资料,查看了网上和官网很多资料,接下来就根据官网教程步骤一步步搭建我的第一个Angular App AngularJS 2说明请参考:http://cnodej ...
- OpenStack对象存储——Swift
OpenStack Object Storage(Swift)是OpenStack开源云计算项目的子项目之一,被称为对象存储,提供了强大的扩展性.冗余和持久性.本文将从架构.原理 和实践等几方面讲述S ...
- mysql存储过程中使用临时表和游标
1.临时表 DROP PROCEDURE IF EXISTS `P_GetMonitorPeople`; CREATE PROCEDURE P_GetMonitorPeople (IN fgid IN ...
- 关于.net类型转换判断问题
做项目时,发现这个问题,由于数据库中的字段可能为null,如果在.net程序直接转换时,比如 ltTime.Text =DateTime.Parse(dt.Rows[0]["m_Time&q ...
- 单点登录CAS使用记(三):实现自定义验证用户登录
问题: CAS自带的用户验证逻辑太过简单,如何像正常网站一样,通过验证DB中的用户数据,来验证用户以及密码的合法性呢? 方案1:CAS默认的JDBC扩展方案: CAS自带了两种简单的通过JDBC方式验 ...
- QT学习:入门篇(三)
头文件: #ifndef CChatDisplaySet_h #define CChatDisplaySet_h #include "QtGui/QDialog" #include ...
- C++采用模板实现栈的方法
今天又看了遍<effective C++>,手动实现了一下条款42中的栈,贴出来当博客的处女贴. 首先栈的声明如下,采用了模板传入类型,而栈的底层采用是个链表. // stack.h // ...
- ECSTORE2.0 定时任务配置
今天在配置ecstore2.0的定时任务时候,发现ECOS后台每30秒通过JS调用 http://localhost/ecstore-bugfix/index.php/shopadmin/index. ...