结合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类创建新的线程。

以上所有设置暴露一个真正的问题。现在,我们虽然使用其他线程发送一个异步消息处理。不幸的是,我们引入竞争条件。

  1. 开始事务
  2. 存储order到数据库
  3. 发送一个包装order的消息
  4. 确认

异步线程获得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中使用异步事件实现同步事务的更多相关文章

  1. spring中的异步事件

    这里讲解一下Spring对异步事件机制的支持,实现方式有两种: 1.全局异步 即只要是触发事件都是以异步执行,具体配置(spring-config-register.xml)如下: Java代码   ...

  2. Spring中ApplicationContext对事件的支持

    Spring中ApplicationContext对事件的支持   ApplicationContext具有发布事件的能力.这是因为该接口继承了ApplicationEventPublisher接口. ...

  3. spring中自定义Event事件的使用和浅析

    在我目前接触的项目中,用到了许多spring相关的技术,框架层面的spring.spring mvc就不说了,细节上的功能也用了不少,如schedule定时任务.Filter过滤器. intercep ...

  4. FPGA设计中的异步复位、同步释放思想

    1.一个简单的异步复位例子: module test( input clk, input rst_n, input data_in, output reg out ); always@(posedge ...

  5. Spring学习之Spring中AOP方式切入声明式事务

    mybatis-spring官方文档说明 一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中.而不是给 MyBatis 创建一个新的 ...

  6. Spring中实现自定义事件

    原理: 通过扩展ApplicationEvent,创建一个事件类CustomEvent.这个类必须定义一个默认的构造函数,它应该从ApplicationEvent类中继承的构造函数. 一旦定义事件类, ...

  7. Spring中的ApplicationContext事件机制

    ApplicationContext的事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListerner接口来实现. 1. 创建EmailEvent pu ...

  8. Spring中基于XML的声明式事务控制配置步骤

    1.配置事务管理器 2.配置事务的通知 此时,我们就需要导入事务的约束 tx名称空间和约束,同时也需要aop的 使用tx:advice标签配置事务通知 属性: id:给事务通知起一个唯一标识 tran ...

  9. Spring 中的事件机制

    说到事件机制,可能脑海中最先浮现的就是日常使用的各种 listener,listener去监听事件源,如果被监听的事件有变化就会通知listener,从而针对变化做相应的动作.这些listener是怎 ...

随机推荐

  1. linux定时任务2-at命令

    定时执行命令: [root@rheltest1 ~]# at now + 1 minute //一分钟后执行下面的任务 at> date >> /tmp/test_date.log ...

  2. 拓扑排序 HDU1285

    这个题是个模板题,可以直接用拓扑排序的模板来做, AC代码 #include <stdio.h> #include<iostream> #include <string. ...

  3. ASP.Net MVC 之FileResult

    FileResult是一个基于文件的ActionResult,利用FileResult我们可以很容易地将从某个物理文件的内容响应给客户端.ASP.NET MVC定义了三个具体的FileResult,分 ...

  4. 定义Foo() 函数,弹出对话框提示当前选中的是第几个单选框

    function foo(){ var ele = document.getElementsByName("radioElement"); for(var i = 0;i<e ...

  5. CUICatalog: Invalid asset name supplied: (null)

    出现这个问题的根本原因是你调用了[UIImage imageNamed:name]这个方法 但是name = nil;所以报出该错误.   解决方法,在项目中搜索[UIImage imageNamed ...

  6. 信息增益(IG,Information Gain)的理解和计算

    决策树构建中节点的选择靠的就是信息增益了. 信息增益是一种有效的特征选择方法,理解起来很简单:增益嘛,肯定是有无这个特征对分类问题的影响的大小,这个特征存在的话,会对分类系统带来多少信息量,缺了他行不 ...

  7. rails 中 create, new, build, save 的用法以及误区汇总

    自己很初级,初级的不能再初级,所以初次接触rails的时候,对于里面的create,new,build等方法不是很了解,用的很混乱,导致经常出现不必要的bug,很苦恼,决定,总结一下,结合网上已有资源 ...

  8. Eclipse关闭XML文件验证的方法,解决xml警告

    XML的编写是否符合规范,可以通过XML Schema或DTD进行验证,但有时候电脑本来就很卡,而且XML的某些错误并未导致程序无法运行的情况下,暂时关闭XML的验证也算不错的选择. 如web.xml ...

  9. mysql 查询copy to tmp table造成堵塞

    show full PROCESSLIST; show VARIABLES like 'tmp_table_size' set GLOBAL tmp_table_size=629145600; SHO ...

  10. The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. (关于jdbc)

    The last packet sent successfully to the server was milliseconds ago. The driver has not received an ...