结合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. 关于centos6.5系统安装FTP服务和配置的方法

    一般在配置服务器的时候,涉及到代码上传,通常都要用到FTP方式. 1.先查看系统是否安装vsftpd: rpm -qa | grep vsftpd 如果出现vsftpd-2.2.2-14......字 ...

  2. 【iOS开发】emoji表情的输入思路

    1.自定义一个表情包类继承NSTextAttachment #import <UIKit/UIKit.h> /** 表情包的自定义类*/ @interface EmojiTextAttac ...

  3. 【转】node-webkit:开发桌面+WEB混合型应用的神器

    顾名思义,node-webkit就是nodejs+webkit. 这样做的好处显而易见,核心奥义在于,用nodejs来进行本地化调用,用webkit来解析和执行HTML+JS. 快速上手 下载node ...

  4. jquery.ajax提交多值(数组)

    偶尔会遇到类似复选框的一个属性存在多值情况,若使用ajax提交的化,设置data :{ids:[1,2,3,4]} 提交后,后台无法使用ids获取到数据. 这里可以用到ajax的 traditiona ...

  5. 获取EnterpriseLibrary企业库配置文件中ConnectionStrings(原创)

    在使用企业类库时想取出单独企业配置文件中的连接字符串遍历,并放到自己的数据库处理类中,在查找了很久的资料后都没有找到,于是自己探索着写了一个,共享给大家以做参考: ConfigurationSourc ...

  6. oracle 复制一条记录只改变主键不写全部列名

    场景:表TEST中有C1,C2,C3...字段,其中C1为主键,先需要复制表TEST中一条(C1='1'的)记录,修改主键列C1和需要变更的列后,再插入到表TEST中. procedure P_TES ...

  7. C++重载操作符

    重载的函数操作符,对对象使用起来就像对象是一个函数一样 class A{public:A(int n);int operator()(int n);  //需要一个参数,返回int类型void out ...

  8. XMLHttpRequest state以及readystate的对应值

    status状态值长整形标准http状态码,定义如下: Number  Description  100 Continue101 Switching PRotocols200 OK201 Create ...

  9. kafka教程

    一.理论介绍(一)相关资料1.官方资料,非常详细:   http://kafka.apache.org/documentation.html#quickstart2.有一篇翻译版,基本一致,有些细节不 ...

  10. java动态缓存技术:WEB缓存应用(转)

    可以实现不等待,线程自动更新缓存 Java动态缓存jar包请下载. 源代码: CacheData.java 存放缓存数据的Bean /** *  */package com.cari.web.cach ...