基于spring如何實(shí)現(xiàn)事件驅(qū)動(dòng)實(shí)例代碼
干貨點(diǎn)
通過(guò)閱讀該篇博客,你可以了解了解java的反射機(jī)制、可以了解如何基于spring生命周期使用自定義注解解決日常研發(fā)問(wèn)題。具體源碼可以點(diǎn)擊鏈接。
問(wèn)題描述
在日常研發(fā)中,經(jīng)常會(huì)遇見(jiàn)業(yè)務(wù)A的某個(gè)action被觸發(fā)后,同時(shí)觸發(fā)業(yè)務(wù)B的action的行為,這種單對(duì)單的形式可以直接在業(yè)務(wù)A的action執(zhí)行結(jié)束后直接調(diào)用業(yè)務(wù)B的action,那么如果是單對(duì)多的情況呢?
方案解決
這里提供一種在日常研發(fā)中經(jīng)常使用到的機(jī)制,基于spring實(shí)現(xiàn)的事件驅(qū)動(dòng),即在業(yè)務(wù)A的action執(zhí)行完,拋出一個(gè)事件,而業(yè)務(wù)B、C、D等監(jiān)聽(tīng)到該事件后處理相應(yīng)的業(yè)務(wù)。
場(chǎng)景范例
這里提供一個(gè)場(chǎng)景范例,該范例基于springboot空殼項(xiàng)目實(shí)現(xiàn),具體可以查看源碼,此處只梳理關(guān)鍵步驟。
步驟一:
定義一個(gè)注解,標(biāo)志接收事件的注解,即所有使用了該注解的函數(shù)都會(huì)在對(duì)應(yīng)事件被拋出的時(shí)候被調(diào)用,該注解實(shí)現(xiàn)比較簡(jiǎn)單,代碼如下
/** * @author xifanxiaxue * @date 3/31/19 * @desc 接收事件的注解 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface ReceiveAnno { // 監(jiān)聽(tīng)的事件 Class clz(); }
如果想了解注解多個(gè)參數(shù)的意義是什么的可以點(diǎn)擊鏈接查看博主之前寫過(guò)文章。
定義事件接口
/** * @author xifanxiaxue * @date 3/31/19 * @desc */ public interface IEvent { }
所有事件都需要實(shí)現(xiàn)該接口,主要是為了后面泛型和類型識(shí)別。
定義MethodInfo
/** * @author xifanxiaxue * @date 3/31/19 * @desc */ public class MethodInfo { public Object obj; public Method method; public static MethodInfo valueOf(Method method, Object obj) { MethodInfo info = new MethodInfo(); info.method = method; info.obj = obj; return info; } public Object getObj() { return obj; } public Method getMethod() { return method; } }
該類只是做了Object和Method的封裝,沒(méi)有其他作用。
步驟二:
實(shí)現(xiàn)一個(gè)事件容器,該容器的作用是存放各個(gè)事件以及需要觸發(fā)的各個(gè)業(yè)務(wù)的method的對(duì)應(yīng)關(guān)系。
/** * @author xifanxiaxue * @date 3/31/19 * @desc 事件容器 */ public class EventContainer { private static Map<Class<IEvent>, List<MethodInfo>> eventListMap = new HashMap<>(); public static void addEventToMap(Class clz, Method method, Object obj) { List<MethodInfo> methodInfos = eventListMap.get(clz); if (methodInfos == null) { methodInfos = new ArrayList<>(); eventListMap.put(clz, methodInfos); } methodInfos.add(MethodInfo.valueOf(method, obj)); } public static void submit(Class clz) { List<MethodInfo> methodInfos = eventListMap.get(clz); if (methodInfos == null) { return; } for (MethodInfo methodInfo : methodInfos) { Method method = methodInfo.getMethod(); try { method.setAccessible(true); method.invoke(methodInfo.getObj()); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } }
其中的addEventToMap函數(shù)的作用是將對(duì)應(yīng)的事件、事件觸發(fā)后需要觸發(fā)的對(duì)應(yīng)業(yè)務(wù)內(nèi)的Method存放在eventListMap內(nèi);而submit函數(shù)會(huì)在其他業(yè)務(wù)類內(nèi)拋出事件的時(shí)候被調(diào)用,而作用是從eventListMap中取出對(duì)應(yīng)的Method,并通過(guò)反射觸發(fā)。
步驟三:
實(shí)現(xiàn)事件處理器,該事件處理器的作用是在bean被spring容器實(shí)例化后去判斷對(duì)應(yīng)的bean是否有相應(yīng)函數(shù)加了@ReceiveAnno注解,如果有則從中取出對(duì)應(yīng)的Event并放入EventContainer中。
/** * @author xifanxiaxue * @date 3/31/19 * @desc 事件處理器 */ @Component public class EventProcessor extends InstantiationAwareBeanPostProcessorAdapter { @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { ReflectionUtils.doWithLocalMethods(bean.getClass(), new ReflectionUtils.MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { ReceiveAnno anno = method.getAnnotation(ReceiveAnno.class); if (anno == null) { return; } Class clz = anno.clz(); try { if (!IEvent.class.isInstance(clz.newInstance())) { FormattingTuple message = MessageFormatter.format("{}沒(méi)有實(shí)現(xiàn)IEvent接口", clz); throw new RuntimeException(message.getMessage()); } } catch (InstantiationException e) { e.printStackTrace(); } EventContainer.addEventToMap(clz, method, bean); } }); return super.postProcessAfterInstantiation(bean, beanName); } }
關(guān)于InstantiationAwareBeanPostProcessorAdapter的描述,有需要的可以查看我之前的文章,其中比較詳細(xì)描述到Spring中的InstantiationAwareBeanPostProcessor類的作用。
步驟四:
對(duì)應(yīng)的業(yè)務(wù)類的實(shí)現(xiàn)如下:
/** * @author xifanxiaxue * @date 3/31/19 * @desc */ @Slf4j @Service public class AFuncService implements IAFuncService { @Override public void login() { log.info("[{}]拋出登錄事件 ... ", this.getClass()); EventContainer.submit(LoginEvent.class); } }
A業(yè)務(wù)類,login會(huì)在被調(diào)用的生活拋出LoginEvent事件。
/** * @author xifanxiaxue * @date 3/31/19 * @desc */ @Service @Slf4j public class BFuncService implements IBFuncService { @ReceiveAnno(clz = LoginEvent.class) private void doAfterLogin() { log.info("[{}]監(jiān)聽(tīng)到登錄事件 ... ", this.getClass()); } }
/** * @author xifanxiaxue * @date 3/31/19 * @desc */ @Service @Slf4j public class CFuncService implements ICFuncService { @ReceiveAnno(clz = LoginEvent.class) private void doAfterLogin() { log.info("[{}]監(jiān)聽(tīng)到登錄事件 ... ", this.getClass()); } }
B和C業(yè)務(wù)類的doAfterLogin都分別加了注解 @ReceiveAnno(clz = LoginEvent.class) ,在監(jiān)聽(tīng)到事件LoginEvent后被觸發(fā)。
為了觸發(fā)方便,我在spring提供的測(cè)試類內(nèi)加了實(shí)現(xiàn),代碼如下:
@RunWith(SpringRunner.class) @SpringBootTest public class EventMechanismApplicationTests { @Autowired private AFuncService aFuncService; @Test public void contextLoads() { aFuncService.login(); } }
可以從中看出啟動(dòng)該測(cè)試類后,會(huì)調(diào)用業(yè)務(wù)A的login函數(shù),而我們要的效果是B業(yè)務(wù)類和C業(yè)務(wù)類的doAfterLogin函數(shù)會(huì)被自動(dòng)觸發(fā),那么結(jié)果如何呢?
結(jié)果打印
我們可以從結(jié)果打印中看到,在業(yè)務(wù)類A的login函數(shù)觸發(fā)后,業(yè)務(wù)類B和業(yè)務(wù)類C都監(jiān)聽(tīng)到了監(jiān)聽(tīng)到登錄事件,證明該機(jī)制正常解決了單對(duì)多的行為觸發(fā)問(wèn)題。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
- Springboot基于enable模塊驅(qū)動(dòng)的實(shí)現(xiàn)
- 詳解Spring Boot Mysql 版本驅(qū)動(dòng)連接池方案選擇
- Spring Bean管理注解方式代碼實(shí)例
- Spring Boot conditional注解用法詳解
- 基于spring@aspect注解的aop實(shí)現(xiàn)過(guò)程代碼實(shí)例
- springboot @WebFilter注解過(guò)濾器的實(shí)現(xiàn)
- Spring實(shí)戰(zhàn)之使用TransactionProxyFactoryBean實(shí)現(xiàn)聲明式事務(wù)操作示例
- spring如何通過(guò)FactoryBean配置Bean
- spring中FactoryBean中的getObject()方法實(shí)例解析
- Spring注解驅(qū)動(dòng)擴(kuò)展原理BeanFactoryPostProcessor
相關(guān)文章
詳解Java中的File文件類以及FileDescriptor文件描述類
在Java中File類可以用來(lái)新建文件和目錄對(duì)象,而FileDescriptor類則被用來(lái)表示文件或目錄的可操作性,接下來(lái)我們就來(lái)詳解Java中的File文件類以及FileDescriptor文件描述類2016-06-06Java將本地項(xiàng)目部署到Linux服務(wù)器的實(shí)踐
本文主要介紹了Java將本地項(xiàng)目部署到Linux服務(wù)器的實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧<BR>2022-06-06SpringBoot解決同名類導(dǎo)致的bean名沖突bean name conflicts問(wèn)題
這篇文章主要介紹了SpringBoot解決同名類導(dǎo)致的bean名沖突bean name conflicts問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06springSecurity之AuthenticationProvider用法解析
這篇文章主要介紹了springSecurity之AuthenticationProvider用法解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Java使用ThreadLocal實(shí)現(xiàn)當(dāng)前登錄信息的存取功能
ThreadLocal和其他并發(fā)工具一樣,也是用于解決多線程并發(fā)訪問(wèn),下這篇文章主要給大家介紹了關(guān)于Java使用ThreadLocal實(shí)現(xiàn)當(dāng)前登錄信息的存取功能,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02request如何獲取body的json數(shù)據(jù)
這篇文章主要介紹了request如何獲取body的json數(shù)據(jù)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Java加密 消息摘要算法SHA實(shí)現(xiàn)詳解
這篇文章主要介紹了Java加密 消息摘要算法SHA實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07