在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是怎 ...
随机推荐
- IPython notebook 使用介绍
参考资料: http://mindonmind.github.io/2013/02/08/ipython-notebook-interactive-computing-new-era/ http:// ...
- 判断两条直线的位置关系 POJ 1269 Intersecting Lines
两条直线可能有三种关系:1.共线 2.平行(不包括共线) 3.相交. 那给定两条直线怎么判断他们的位置关系呢.还是用到向量的叉积 例题:POJ 1269 题意:这道题是给定四个点p1, ...
- 渲染器 Shader BitmapShader
渲染模式: tileX tileY:The tiling mode for x/y to draw the bitmap in. 在位图上 X/Y 方向 瓦工/花砖/瓷砖 模式 CLAMP :如 ...
- GSON 示例代码 实用版
去除所有格式的json字符串 {"data":[{"friend":[{"address":"广州","nam ...
- MVC4数据访问EF查询linq语句的时候报错找不到表名问题
一天做项目的时候遇到这样的问题,MVC4用EF访问数据查询用linq语句的时候报错找不到表名:报错如下图: 研究了几种情况,最后还是没有找到正真的问题所在,不过可能是和路由解析问题有关,暂时还没有进行 ...
- dl标签和table标签
dl标签定义了一个定义列表 <html> <body> <h2>一个定义列表:</h2> <dl> <dt>计算机</ ...
- ORACLE外键和锁
在oracle中,如果外键未加索引,对父表的修改,会导致子表被加上全表锁.这包括两种情况: 1.删除父表中的行,如果外键上没有索引,会导致子表被加上全表锁 2.更新父表的主键(根据关系数据库的原则,更 ...
- 使用UILocalNotification给App添加本地消息通知
使用过的代码,直接贴上 UILocalNotification *notification = [[UILocalNotification alloc] init]; if (notification ...
- Python 快捷键
Ctrl + [ .Ctrl + ] 缩进代码Alt+3 Alt+4 注释.取消注释代码行Alt+5 Alt+6 切换缩进方式 空格<=>TabAlt+/ 单词完成,只要文中出现过,就可以 ...
- linux内核学习之进程管理------task_struct结构体
struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ struct t ...