1. 程式人生 > 其它 >在SpringBoot中實現非同步事件驅動

在SpringBoot中實現非同步事件驅動

在專案實際開發過程中,我們有很多這樣的業務場景:一個事務中處理完一個業務邏輯後需要跟著處理另外一個業務邏輯,偽碼大致如下:

@Service
public class ProductServiceImpl {
    ...
    public void saveProduct(Product product) {
        productMapper.saveOrder(product);
        notifyService.notify(product);
    }
    ...
}

很簡單並且很常見的一段業務邏輯:首先將產品先儲存資料庫,然後傳送通知。

某一天你們可能需要把新增的產品存到Es中,這時候也需要程式碼可能變成這樣:

@Service
public class ProductServiceImpl {
    ...
    public void saveProduct(Product product) {
        productMapper.saveProduct(product);
        esService.saveProduct(product)
        notifyService.notify(product);
    }
    ...
}

隨著業務需求的變化,程式碼也需要跟著一遍遍的修改。而且還會存在另外一個問題,如果通知系統掛了,那就不能再新增產品了。

對於上面這種情況非常適合引入訊息中介軟體(訊息佇列)來對業務進行解耦,但並非所有的業務系統都會引入訊息中介軟體(引入會第三方架構元件會帶來很大的運維成本)。

Spring提供了事件驅動機制可以幫助我們實現這一需求。

Spring事件驅動
spring事件驅動由3個部分組成:

  1. ApplicationEvent:表示事件本身,自定義事件需要繼承該類,用來定義事件
  2. ApplicationEventPublisher:事件傳送器,主要用來發布事件
  3. ApplicationListener:事件監聽器介面,監聽類實現ApplicationListener 裡onApplicationEvent方法即可,也可以在方法上增加@EventListener以實現事件監聽。

實現Spring事件驅動一般只需要三步:

  1. 自定義需要釋出的事件類,需要繼承ApplicationEvent類
  2. 使用ApplicationEventPublisher來發布自定義事件
  3. 使用@EventListener來監聽事件

這裡需要特別注意一點,預設情況下事件是同步的。即事件被publish後會等待Listener的處理。如果釋出事件處的業務存在事務,監聽器處理也會在相同的事務中。如果需要非同步處理事件,可以onApplicationEvent方法上加@Aync支援非同步或在有@EventListener的註解方法上加上@Aync

原始碼實戰

  • 建立事件
public class ProductEvent extends ApplicationEvent {
    public ProductEvent(Product product) {
        super(product);
    }
}
  • 釋出事件
@Service
public class ProductServiceImpl implements IproductService {
    ...
    @Autowired
    private ApplicationEventPublisher publisher;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveProduct(Product product) {
        productMapper.saveProduct(product);    
        //事件釋出
        publisher.publishEvent(product);
    }
    ...
}
  • 事件監聽
@Slf4j
@AllArgsConstructor
public class ProductListener {

    private final NotifyService notifyServcie;

    @Async
    @Order
    @EventListener(ProductEvent.class)
    public void notify(ProductEvent event) {
        Product product = (Product) event.getSource();
        notifyServcie.notify(product, "product");
    }
}
  • 在SpringBoot啟動類上增加@EnableAsync註解
@Slf4j
@EnableSwagger2
@SpringBootApplication
@EnableAsync
public class ApplicationBootstrap {
...
}

注意:使用了Async後會使用預設的執行緒池SimpleAsyncTaskExecutor,一般我們會在專案中自定義一個執行緒池。