寫在前面:2020年面試必備的Java後端進階面試題總結了一份複習指南在Github上,內容詳細,圖文並茂,有需要學習的朋友可以Star一下!
GitHub地址:https://github.com/abel-max/Java-Study-Note/tree/master
在項目實際開發過程中,我們有很多這樣的業務場景:一個事務中處理完一個業務邏輯後需要跟着處理另外一個業務邏輯,僞碼大致如下:
@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個部分組成
- ApplicationEvent:表示事件本身,自定義事件需要繼承該類,用來定義事件
- ApplicationEventPublisher:事件發送器,主要用來發布事件
- ApplicationListener:事件監聽器接口,監聽類實現ApplicationListener 裏onApplicationEvent方法即可,也可以在方法上增加@EventListener以實現事件監聽。
實現Spring事件驅動一般只需要三步:
- 自定義需要發佈的事件類,需要繼承ApplicationEvent類
- 使用ApplicationEventPublisher來發布自定義事件
- 使用@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,一般我們會在項目中自定義一個線程池。
@Configuration
public class ExecutorConfig {
/** 核心線程數 */
private int corePoolSize = 10;
/** 最大線程數 */
private int maxPoolSize = 50;
/** 隊列大小 */
private int queueCapacity = 10;
/** 線程最大空閒時間 */
private int keepAliveSeconds = 150;
@Bean("customExecutor")
public Executor myExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("customExecutor-");
executor.setKeepAliveSeconds(keepAliveSeconds);
// rejection-policy:當pool已經達到max size的時候,如何處理新任務
// CALLER_RUNS:不在新線程中執行任務,而是由調用者所在的線程來執行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}