在Spring環(huán)境中事件驅(qū)動(dòng)代碼的示例分析,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。
目前創(chuàng)新互聯(lián)已為千余家的企業(yè)提供了網(wǎng)站建設(shè)、域名、網(wǎng)頁(yè)空間、網(wǎng)站運(yùn)營(yíng)、企業(yè)網(wǎng)站設(shè)計(jì)、剛察網(wǎng)站維護(hù)等服務(wù),公司將堅(jiān)持客戶導(dǎo)向、應(yīng)用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長(zhǎng),共同發(fā)展。
雖然現(xiàn)在的各種應(yīng)用都是集群部署,單機(jī)部署的應(yīng)用越來(lái)越少了,但是不可否認(rèn)的是,市場(chǎng)上還是存在許多單機(jī)應(yīng)用的。本文要介紹的是 Guava 中的 EventBus 的使用。
EventBus 處理的事情類似觀察者模式,基于事件驅(qū)動(dòng),觀察者們監(jiān)聽(tīng)自己感興趣的特定事件,進(jìn)行相應(yīng)的處理。
在 Spring 環(huán)境中優(yōu)雅地使用 Guava 包中的 EventBus,對(duì)我們的代碼進(jìn)行一定程度的解耦。
Step 0:添加 Guava 依賴 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>22.0</version> </dependency> 作為 java 程序員,如果你還沒(méi)有使用過(guò) Google Guava,請(qǐng)從現(xiàn)在開(kāi)始將它加到你的每一個(gè)項(xiàng)目中。
Step 1:定義一個(gè)注解用于標(biāo)記 listener /**
用于標(biāo)記 listener */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface EventBusListener { } Step 2:定義注冊(cè)中心 package com.javadoop.eventbus;
import java.util.List; import java.util.concurrent.Executors; import javax.annotation.PostConstruct; import org.springframework.stereotype.Component; import com.google.common.eventbus.AsyncEventBus; import com.google.common.eventbus.EventBus; import com.hongjiev.javadoop.util.SpringContextUtils;
@Component public class EventBusCenter {
// 管理同步事件 private EventBus syncEventBus = new EventBus(); // 管理異步事件 private AsyncEventBus asyncEventBus = new AsyncEventBus(Executors.newCachedThreadPool()); public void postSync(Object event) { syncEventBus.post(event); } public void postAsync(Object event) { asyncEventBus.post(event); } @PostConstruct public void init() { // 獲取所有帶有 @EventBusListener 的 bean,將他們注冊(cè)為監(jiān)聽(tīng)者 List<Object> listeners = SpringContextUtils.getBeansWithAnnotation(EventBusListener.class); for (Object listener : listeners) { asyncEventBus.register(listener); syncEventBus.register(listener); } }
} Step 3:定義各種事件 舉個(gè)例子,我們定義一個(gè)訂單創(chuàng)建事件:
package com.javadoop.eventbus.event;
public class OrderCreatedEvent { private long orderId; private long userId; public OrderCreatedEvent(long orderId, long userId) { this.setOrderId(orderId); this.setUserId(userId); } // getter、setter } Step 4:定義事件監(jiān)聽(tīng)器 首先,類上面需要加我們之前定義的注解:@EventBusListener,然后監(jiān)聽(tīng)方法需要加注解 @Subscribe,方法參數(shù)為具體事件。
package com.javadoop.eventbus.listener;
import org.springframework.stereotype.Component; import com.google.common.eventbus.Subscribe; import com.javadoop.eventbus.EventBusListener; import com.javadoop.eventbus.event.OrderCreatedEvent;
@Component @EventBusListener public class OrderChangeListener {
@Subscribe public void created(OrderCreatedEvent event) { long orderId = event.getOrderId(); long userId = event.getUserId(); // 訂單創(chuàng)建成功后的各種操作,如發(fā)短信、發(fā)郵件等等。 // 注意,事件可以被訂閱多次,也就是說(shuō)可以有很多方法監(jiān)聽(tīng) OrderCreatedEvent 事件, // 所以沒(méi)必要在一個(gè)方法中處理發(fā)短信、發(fā)郵件、更新庫(kù)存等 } @Subscribe public void change(OrderChangeEvent event) { // 處理訂單變化后的修改 // 如發(fā)送提醒、更新物流等 }
} Step 5:發(fā)送事件 @Service public class OrderService {
@Autowired private EventBusCenter eventBusCenter; public void createOrder() { // 處理創(chuàng)建訂單 // ... // 發(fā)送異步事件 eventBusCenter.postAsync(new OrderCreatedEvent(1L, 1L)); }
} 總結(jié) EventBus 的好處在于,它將發(fā)生事件的代碼和事件處理的代碼進(jìn)行了解耦。
比如系統(tǒng)中很多地方都會(huì)修改訂單,用戶可以自己修改、客服也可以修改、甚至可能是團(tuán)購(gòu)沒(méi)成團(tuán)系統(tǒng)進(jìn)行的訂單修改,所有這些觸發(fā)訂單修改的地方都要發(fā)短信、發(fā)郵件,假設(shè)以后還要增加其他操作,那么需要修改的地方就比較多。
而如果采用事件驅(qū)動(dòng)的話,只要這些地方拋出事件就可以了,后續(xù)的維護(hù)是比較簡(jiǎn)單的。
而且,EventBus 支持同步事件和異步事件,可以滿足我們不同場(chǎng)景下的需求。比如發(fā)短信,系統(tǒng)完全沒(méi)必要等在那邊,完全是可以異步做的。
附錄:SpringContextUtils 上面的代碼使用到了 SpringContextUtils,我想大部分的 Spring 應(yīng)用都會(huì)寫這么一個(gè)工具類來(lái)從 Spring 容器中獲取 Bean,用于一些不方便采用注入的地方。
@Component public class SpringContextUtils implements BeanFactoryPostProcessor {
private static ConfigurableListableBeanFactory beanFactory; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { SpringContextUtils.beanFactory = configurableListableBeanFactory; } public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } public static <T> T getBean(Class<T> clz) throws BeansException { T result = beanFactory.getBean(clz); return result; } public static <T> List<T> getBeansOfType(Class<T> type) { return beanFactory.getBeansOfType(type).entrySet().stream().map(entry->entry.getValue()).collect(Collectors.toList()); } // 上面的例子用到了這個(gè) public static List<Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) { Map<String, Object> beansWithAnnotation = beanFactory.getBeansWithAnnotation(annotationType); // java 8 的寫法,將 map 的 value 收集起來(lái)到一個(gè) list 中 return beansWithAnnotation.entrySet().stream().map(entry->entry.getValue()).collect(Collectors.toList()); // java 7 List<Object> result = new ArrayList<>(); for (Map.Entry<String, Object> entry : beansWithAnnotation.entrySet()) { result.add(entry.getValue()); } return result; }
這次重新又用上了這個(gè) eventbus,碰到一個(gè)新的問(wèn)題:在使用同步事件的時(shí)候,怎樣將事件處理過(guò)程中拋出來(lái)的異常拋回給客戶端?
首先我們要明白,AsyncEventBus 是異步模式的,EventBus 是同步模式的,在使用同步模式的時(shí)候,線程 post 一個(gè) event 以后,還是由當(dāng)前線程來(lái)處理各個(gè) Subscriber 中的操作的。
所以在調(diào)用 void eventbus.post(event) 這個(gè)方法后,線程會(huì)先去處理 Subscribers 中的操作,處理完了以后,post(event) 方法才會(huì)返回。
StackOverflow 上有很多人都碰到了這個(gè)問(wèn)題,不過(guò)我沒(méi)有找到合適的解決方案,就自己造了一個(gè)。
解決方法很簡(jiǎn)單,就是使用 ThreadLocal 來(lái)傳遞異常:
ThreadLocal<ServiceException> threadLocal = new ThreadLocal();
/**
管理同步事件 */ private EventBus syncEventBus = new EventBus(new SubscriberExceptionHandler() {
@Override public void handleException(Throwable exception, SubscriberExceptionContext context) { if (exception instanceof ServiceException) { threadLocal.set((ServiceException) exception); } } });
public void postSync(Object event) { syncEventBus.post(event); ServiceException ex = threadLocal.get(); if (ex != null) { // 記得 remove threadLocal.remove(); throw ex; } } ps: 在多個(gè) Subscriber 的場(chǎng)景中,在一個(gè) Subscriber 中拋出異常,不會(huì)阻止線程執(zhí)行下一個(gè) Subscriber 中的操作。在上面的代碼中,如果有多個(gè) Subscriber 拋出異常,就是 threadLocal 會(huì)被設(shè)置多次,最終得到的是最后一個(gè) ex 的值。
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)的支持。
本文標(biāo)題:在Spring環(huán)境中事件驅(qū)動(dòng)代碼的示例分析
分享URL:http://muchs.cn/article10/ihipgo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開(kāi)發(fā)、網(wǎng)站建設(shè)、網(wǎng)站維護(hù)、外貿(mào)網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站建設(shè)、動(dòng)態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)