干貨點
通過閱讀該篇博客,你可以了解了解java的反射機制、可以了解如何基于spring生命周期使用自定義注解解決日常研發問題。具體源碼可以點擊鏈接。
問題描述
在日常研發中,經常會遇見業務a的某個action被觸發后,同時觸發業務b的action的行為,這種單對單的形式可以直接在業務a的action執行結束后直接調用業務b的action,那么如果是單對多的情況呢?
方案解決
這里提供一種在日常研發中經常使用到的機制,基于spring實現的事件驅動,即在業務a的action執行完,拋出一個事件,而業務b、c、d等監聽到該事件后處理相應的業務。
場景范例
這里提供一個場景范例,該范例基于springboot空殼項目實現,具體可以查看源碼,此處只梳理關鍵步驟。
步驟一:
定義一個注解,標志接收事件的注解,即所有使用了該注解的函數都會在對應事件被拋出的時候被調用,該注解實現比較簡單,代碼如下
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/** * @author xifanxiaxue * @date 3/31/19 * @desc 接收事件的注解 */ @documented @retention (retentionpolicy.runtime) @target ({elementtype.method}) public @interface receiveanno { // 監聽的事件 class clz(); } |
如果想了解注解多個參數的意義是什么的可以點擊鏈接查看博主之前寫過文章。
定義事件接口
1
2
3
4
5
6
7
|
/** * @author xifanxiaxue * @date 3/31/19 * @desc */ public interface ievent { } |
所有事件都需要實現該接口,主要是為了后面泛型和類型識別。
定義methodinfo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/** * @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的封裝,沒有其他作用。
步驟二:
實現一個事件容器,該容器的作用是存放各個事件以及需要觸發的各個業務的method的對應關系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/** * @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函數的作用是將對應的事件、事件觸發后需要觸發的對應業務內的method存放在eventlistmap內;而submit函數會在其他業務類內拋出事件的時候被調用,而作用是從eventlistmap中取出對應的method,并通過反射觸發。
步驟三:
實現事件處理器,該事件處理器的作用是在bean被spring容器實例化后去判斷對應的bean是否有相應函數加了@receiveanno注解,如果有則從中取出對應的event并放入eventcontainer中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
/** * @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( "{}沒有實現ievent接口" , clz); throw new runtimeexception(message.getmessage()); } } catch (instantiationexception e) { e.printstacktrace(); } eventcontainer.addeventtomap(clz, method, bean); } }); return super .postprocessafterinstantiation(bean, beanname); } } |
關于instantiationawarebeanpostprocessoradapter的描述,有需要的可以查看我之前的文章,其中比較詳細描述到spring中的instantiationawarebeanpostprocessor類的作用。
步驟四:
對應的業務類的實現如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/** * @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業務類,login會在被調用的生活拋出loginevent事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/** * @author xifanxiaxue * @date 3/31/19 * @desc */ @service @slf4j public class bfuncservice implements ibfuncservice { @receiveanno (clz = loginevent. class ) private void doafterlogin() { log.info( "[{}]監聽到登錄事件 ... " , this .getclass()); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/** * @author xifanxiaxue * @date 3/31/19 * @desc */ @service @slf4j public class cfuncservice implements icfuncservice { @receiveanno (clz = loginevent. class ) private void doafterlogin() { log.info( "[{}]監聽到登錄事件 ... " , this .getclass()); } } |
b和c業務類的doafterlogin都分別加了注解 @receiveanno(clz = loginevent.class) ,在監聽到事件loginevent后被觸發。
為了觸發方便,我在spring提供的測試類內加了實現,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
@runwith (springrunner. class ) @springboottest public class eventmechanismapplicationtests { @autowired private afuncservice afuncservice; @test public void contextloads() { afuncservice.login(); } } |
可以從中看出啟動該測試類后,會調用業務a的login函數,而我們要的效果是b業務類和c業務類的doafterlogin函數會被自動觸發,那么結果如何呢?
結果打印
我們可以從結果打印中看到,在業務類a的login函數觸發后,業務類b和業務類c都監聽到了監聽到登錄事件,證明該機制正常解決了單對多的行為觸發問題。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對服務器之家的支持。
原文鏈接:https://juejin.im/post/5ca9c0bf6fb9a05e462ba780