最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot實(shí)現(xiàn)異步事件Event詳解

 更新時(shí)間:2023年11月10日 08:50:48   作者:木棉軟糖  
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)異步事件Event詳解,異步事件的模式,通常將一些非主要的業(yè)務(wù)放在監(jiān)聽器中執(zhí)行,因?yàn)楸O(jiān)聽器中存在失敗的風(fēng)險(xiǎn),所以使用的時(shí)候需要注意,需要的朋友可以參考下

SpringBoot實(shí)現(xiàn)異步事件

為什么需要用到Spring Event?

我簡單說一個(gè)場景,大家都能明白: 你在公司內(nèi)部,寫好了一個(gè)用戶注冊的功能

然后產(chǎn)品經(jīng)理根據(jù)公司情況,新增以下需求

  1. 注冊新用戶,給新用戶發(fā)郵件
  2. 發(fā)放新用戶優(yōu)惠券
public void registerUser(AddUserRequest request){
	//插入用戶
	userService.insertUser(request);
}

實(shí)現(xiàn)需求后:

public void registerUser(AddUserRequest request){
	//插入用戶
	User user = convertToUser(request)
	userService.insertUser(user);
	//發(fā)郵件
	sendEmail(user);
	//發(fā)放優(yōu)惠券
	sendCouponToUser(user);
}

這樣正常寫的話,會有以下缺點(diǎn):

  1. 發(fā)郵件方法里面,如果郵件服務(wù)出現(xiàn)問題,就會影響到注冊用戶的核心業(yè)務(wù),無論發(fā)郵件成不成功,都不應(yīng)影響注冊用戶
  2. 發(fā)放優(yōu)惠券,產(chǎn)品經(jīng)理會根據(jù)市場需求要求你反復(fù)去掉刪除,要是沒有一些措施,很容易被產(chǎn)品經(jīng)理"耍猴",而且反復(fù)改代碼會導(dǎo)致功能不穩(wěn)定。

更理論的話來說,就是把一些次要的功能耦合到核心功能里面,且經(jīng)常調(diào)整,會導(dǎo)致核心功能不穩(wěn)定

解決方案: 將發(fā)放優(yōu)惠券,發(fā)送郵件做成單獨(dú)的服務(wù)A和B。 注冊業(yè)務(wù)在注冊用戶成功后,發(fā)布一個(gè)"注冊成功"的消息。

服務(wù)A和服務(wù)B相當(dāng)于一個(gè)監(jiān)聽者,都監(jiān)聽**"注冊成功"的消息**,監(jiān)聽到后,服務(wù)A和B就各自做自己的事情了。 服務(wù)A和服務(wù)B不需要關(guān)心到底是誰,哪個(gè)地方發(fā)出了這個(gè)消息,它只需要監(jiān)聽此消息并做出反應(yīng)。

這種方式的好處是:

  1. 如果不想要發(fā)放優(yōu)惠券的功能,直接把服務(wù)A的代碼去掉就好了,而且由于跟注冊用戶解耦,可以不用擔(dān)心影響到注冊功能。
  2. 如果想要做更多的次要業(yè)務(wù),例如注冊時(shí)發(fā)短信通知,可以增加一個(gè)服務(wù)C監(jiān)聽**"注冊成功"的消息**,然后服務(wù)C進(jìn)行自己的服務(wù)就行。不需要更改注冊用戶的代碼。

上面這種模式就是事件模式。

Spring Event 的使用

注解方式實(shí)現(xiàn)

我用注解的方式去實(shí)現(xiàn)Spring Event的使用 事件對象:

@Data
public class RegisterUserEvent {
    /**
     * 用戶id
     */
    private Integer userId;
    /**
     * 用戶名
     */
    private String userName;
}

接口:

@RestController
@Api(tags="測試前端控制器")
@RequiredArgsConstructor
public class TestController {
    private final TestService testService;

    @ApiOperation(value="模擬注冊用戶功能的發(fā)送事件", notes="\n 開發(fā)者:")
    @PostMapping("/sendEvent")
    public JsonResult sendEvent(){
        testService.sendEvent();
        return JsonResult.success();
    }
}

注冊功能:

/**
 * @author zhengbingyuan
 * @date 2023/2/6
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TestService {
    private final ApplicationEventPublisher eventPublisher;

    /**
     * 模擬一個(gè)注冊用戶的功能
     */
    @Transactional(rollbackFor = Exception.class)
    public void sendEvent() {
        log.info("開始注冊用戶....");
        UserDto dto = saveUser();

        RegisterUserEvent userEvent = new RegisterUserEvent();
        userEvent.setUserId(dto.getId());
        userEvent.setUserName(dto.getUserName());
        eventPublisher.publishEvent(userEvent);
    }

    private UserDto saveUser() {
        int id = 1;
        String userName = "超人";
        log.info("保存用戶id: {},name:{}",id,userName);
        UserDto dto = new UserDto();
        dto.setId(id);
        dto.setUserName(userName);
        return dto;
    }


}

次要業(yè)務(wù)的事件監(jiān)聽:

/**
 * @author zhengbingyuan
 * @date 2023/2/6
 */
@Slf4j
@Component
public class RegisterUserEventListener {
    @EventListener
    public void processSendCouponToUser(RegisterUserEvent event){
        log.info("發(fā)放優(yōu)惠券給用戶:{}",event.getUserName());
    }


    @EventListener
    public void processSendEmailToUser(RegisterUserEvent event){
        log.info("發(fā)放郵件給用戶:{}",event.getUserName());
    }
}

結(jié)果:

2023-02-06 16:47:30,228:INFO  http-nio-8083-exec-2 [] (TestService.java:28) - 開始注冊用戶....
2023-02-06 16:47:30,229:INFO  http-nio-8083-exec-2 [] (TestService.java:40) - 保存用戶id: 1,name:超人
2023-02-06 16:47:30,232:INFO  http-nio-8083-exec-2 [] (RegisterUserEventListener.java:17) - 發(fā)放優(yōu)惠券給用戶:超人
2023-02-06 16:47:30,232:INFO  http-nio-8083-exec-2 [] (RegisterUserEventListener.java:23) - 發(fā)放郵件給用戶:超人

小結(jié)

上面將注冊的主要邏輯(用戶信息落庫)和次要的業(yè)務(wù)邏輯(發(fā)送郵件)通過事件的方式解耦了。次要的業(yè)務(wù)做成了可插拔的方式,比如不想發(fā)送郵件了,只需要將郵件監(jiān)聽器上面的@Component注釋就可以了,非常方便擴(kuò)展。

Spring Event異步模式

對于上面的程序,如果發(fā)送郵件出現(xiàn)異常的話,根據(jù)實(shí)踐,整個(gè)注冊功能會受到影響,也就是上面的程序僅只實(shí)現(xiàn)了代碼可拔插的效果。 如果將發(fā)送郵件這一個(gè)功能完全解耦出來,還需要做成異步事件模式。

先看看事件監(jiān)聽器是怎么實(shí)現(xiàn)的 在注解方式的publishEvent方法底層,會通過getApplicationEventMulticaster().multicastEvent(event)來派發(fā)事件。這個(gè)getApplicationEventMulticaster()獲得的對象是SimpleApplicationEventMulticaster。

SimpleApplicationEventMulticaster 里面有一個(gè)taskExecutor 的線程池,如果這個(gè)線程池不是null,那么將會使用這個(gè)線程池去消費(fèi)事件消息。

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	Executor executor = getTaskExecutor();
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		if (executor != null) {
			//線程池調(diào)用
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
			//直接調(diào)用
			invokeListener(listener, event);
		}
	}
}

所以,只要讓executor 不為null,就能使用異步事件了。但是默認(rèn)情況下executor是空的,此時(shí)需要我們來給其設(shè)置一個(gè)值。

怎么設(shè)置這個(gè)值,這需要看回去ApplicationEventMulticaster是怎么初始化的,這個(gè)對象是在AbstractApplication.refresh()中的initApplicationEventMulticaster()方法執(zhí)行。

protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isTraceEnabled()) {
				logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
		else {
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
			beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
			if (logger.isTraceEnabled()) {
				logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
						"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
			}
		}
	}

通過初始化方法,可以得知,只要存在name = "applicationEventMulticaster" 的bean,那么就不會創(chuàng)建SimpleApplicationEventMulticaster 實(shí)例。 換句話說,只要開發(fā)者在配置類,提供一個(gè)設(shè)置好taskExecutor的SimpleApplicationEventMulticaster 就可以使用異步事件了。

/**
 * @author zhengbingyuan
 * @date 2023/2/6
 */
@Configuration
@RequiredArgsConstructor
public class AsyncEventConfiguration {
    @Bean
    public SimpleApplicationEventMulticaster applicationEventMulticaster(BeanFactory beanFactory) {
        SimpleApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        //設(shè)置線程池
        applicationEventMulticaster.setTaskExecutor(eventExecutor());
        return applicationEventMulticaster;
    }

    @Bean
    public TaskExecutor eventExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        //核心線程數(shù)
        int corePoolSize = 5;
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        //最大線程數(shù)
        int maxPoolSize = 10;
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
        //隊(duì)列容量
        int queueCapacity = 10;
        threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        //拒絕策略
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        //線程名前綴
        String threadNamePrefix = "eventExecutor-";
        threadPoolTaskExecutor.setThreadNamePrefix(threadNamePrefix);
        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        // 使用自定義的跨線程的請求級別線程工廠類19
        int awaitTerminationSeconds = 5;
        threadPoolTaskExecutor.setAwaitTerminationSeconds(awaitTerminationSeconds);
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

繼續(xù)使用上面所說的例子,由于我log日志有加線程前綴,這里就不用加線程阻塞手段去測試了。

結(jié)果:可以看出,次要業(yè)務(wù)和核心業(yè)務(wù)已經(jīng)是發(fā)生在不同的線程上了

2023-02-06 18:22:19,865:INFO  http-nio-8083-exec-2 [] (TestService.java:28) - 開始注冊用戶....
2023-02-06 18:22:19,866:INFO  http-nio-8083-exec-2 [] (TestService.java:41) - 保存用戶id: 1,name:超人
2023-02-06 18:22:19,866:INFO  http-nio-8083-exec-2 [] (TestService.java:35) - 注冊用戶完成
2023-02-06 18:22:19,866:INFO  eventExecutor-3 [] (RegisterUserEventListener.java:17) - 發(fā)放優(yōu)惠券給用戶:超人
2023-02-06 18:22:19,866:INFO  eventExecutor-7 [] (RegisterUserEventListener.java:23) - 發(fā)放郵件給用戶:超人

小結(jié): 異步線程的使用,在次要業(yè)務(wù)代碼可拔插的情況下,進(jìn)一步解耦,即使次要業(yè)務(wù)出問題,也不影響核心業(yè)務(wù)。

事件使用建議

異步事件的模式,通常將一些非主要的業(yè)務(wù)放在監(jiān)聽器中執(zhí)行,因?yàn)楸O(jiān)聽器中存在失敗的風(fēng)險(xiǎn),所以使用的時(shí)候需要注意。

如果只是為了解耦,但是被解耦的次要業(yè)務(wù)也是必須要成功的,可以使用消息中間件的方式(落地+重試機(jī)制)來解決這些問題。

到此這篇關(guān)于SpringBoot實(shí)現(xiàn)異步事件Event詳解的文章就介紹到這了,更多相關(guān)SpringBoot實(shí)現(xiàn)異步事件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java開發(fā)中StepBuilder模式詳解

    Java開發(fā)中StepBuilder模式詳解

    Step builder多階段步驟構(gòu)造器模式是一種對象創(chuàng)建軟件設(shè)計(jì)模式,與傳統(tǒng)構(gòu)建器模式進(jìn)行比較時(shí),步驟構(gòu)建器模式提供了一些簡潔的好處,這篇文章主要介紹了Java開發(fā)中StepBuilder模式的相關(guān)資料,需要的朋友可以參考下
    2026-01-01
  • java 通過聚合查詢實(shí)現(xiàn)elasticsearch的group by后的數(shù)量

    java 通過聚合查詢實(shí)現(xiàn)elasticsearch的group by后的數(shù)量

    這篇文章主要介紹了java 通過聚合查詢實(shí)現(xiàn)elasticsearch的group by后的數(shù)量,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-12-12
  • Mybatis中Mapper標(biāo)簽總結(jié)大全

    Mybatis中Mapper標(biāo)簽總結(jié)大全

    這篇文章主要介紹了Mybatis中Mapper標(biāo)簽總結(jié)大全,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • kafka提交偏移量失敗導(dǎo)致重復(fù)消費(fèi)的解決

    kafka提交偏移量失敗導(dǎo)致重復(fù)消費(fèi)的解決

    文章主要討論了在使用Spring Kafka時(shí)遇到的`KafkaException`,特別是與消費(fèi)者組和偏移量提交相關(guān)的問題,文章解釋了Kafka消費(fèi)者的心跳機(jī)制和`max.poll.interval.ms`配置的作用,并提供了如何在`application.yml`或`application.properties`文件中配置這些參數(shù)的示例
    2026-01-01
  • Springboot集成JSR303參數(shù)校驗(yàn)的方法實(shí)現(xiàn)

    Springboot集成JSR303參數(shù)校驗(yàn)的方法實(shí)現(xiàn)

    這篇文章主要介紹了Springboot集成JSR303參數(shù)校驗(yàn)的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • Java?Excel?Poi字體顏色自定義設(shè)置代碼

    Java?Excel?Poi字體顏色自定義設(shè)置代碼

    最近項(xiàng)目使用POI按模板導(dǎo)出Excel,需要設(shè)置單元格的字體為紅色,下面這篇文章主要給大家介紹了關(guān)于Java?Excel?Poi字體顏色自定義設(shè)置的相關(guān)資料,需要的朋友可以參考下
    2024-01-01
  • 關(guān)于弗洛伊德算法求最短路徑詳解

    關(guān)于弗洛伊德算法求最短路徑詳解

    這篇文章主要介紹了關(guān)于弗洛伊德算法求最短路徑詳解,弗洛伊德算法VS迪杰斯特拉算法:迪杰斯特拉算法通過選定的被訪問頂點(diǎn),求出從出發(fā)訪問頂點(diǎn)到其他項(xiàng)點(diǎn)的最短路徑:弗洛伊德算法中每-個(gè)頂點(diǎn)都是出發(fā)訪問點(diǎn),需要的朋友可以參考下
    2023-07-07
  • Java 語言實(shí)現(xiàn)清除帶 html 標(biāo)簽的內(nèi)容方法

    Java 語言實(shí)現(xiàn)清除帶 html 標(biāo)簽的內(nèi)容方法

    下面小編就為大家?guī)硪黄狫ava 語言實(shí)現(xiàn)清除帶 html 標(biāo)簽的內(nèi)容方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-02-02
  • @Scheduled注解不能同時(shí)執(zhí)行多個(gè)定時(shí)任務(wù)的解決方案

    @Scheduled注解不能同時(shí)執(zhí)行多個(gè)定時(shí)任務(wù)的解決方案

    這篇文章主要介紹了@Scheduled注解不能同時(shí)執(zhí)行多個(gè)定時(shí)任務(wù)的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • SpringBoot中EasyExcel實(shí)現(xiàn)Excel文件的導(dǎo)入導(dǎo)出

    SpringBoot中EasyExcel實(shí)現(xiàn)Excel文件的導(dǎo)入導(dǎo)出

    這篇文章主要介紹了SpringBoot中EasyExcel實(shí)現(xiàn)Excel文件的導(dǎo)入導(dǎo)出,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10

最新評論

西盟| 农安县| 行唐县| 四平市| 岱山县| 渝中区| 武乡县| 灵寿县| 襄垣县| 塔城市| 景洪市| 定边县| 谢通门县| 东乌珠穆沁旗| 阿拉善盟| 洮南市| 武强县| 黄骅市| 安新县| 尉犁县| 化州市| 泗阳县| 英吉沙县| 马尔康县| 信阳市| 怀柔区| 安化县| 莎车县| 枝江市| 城市| 高密市| 陵水| 上高县| 平乡县| 泰和县| 宾川县| 樟树市| 子长县| 郎溪县| 武安市| 金湖县|