柳暗花明又一村——dubbo事件通知踩坑

前言
原本今天是想分享dubbo的事件通知機(jī)制的,但是試了好多次,一直都沒(méi)有成功,最后看到官方給出的回復(fù):
oninvoke ,onreturn,onthrow do work well in xml ,but do not work when use annotation
也就是說(shuō)事件通知在注解模式下不支持,所以我就不打算繼續(xù)研究了,我現(xiàn)在就只想研究注解模式,感興趣的小伙伴自己去看下,這里我們就簡(jiǎn)單介紹下事件通知機(jī)制。
事件通知
事件通知機(jī)制簡(jiǎn)單來(lái)說(shuō)就是針對(duì)在調(diào)用之前、調(diào)用之后、出現(xiàn)異常時(shí)的時(shí)間通知,就是我們?nèi)藶橹付ǖ幕卣{(diào)函數(shù),從2.0.7以后的版本開(kāi)始支持,我想后續(xù)應(yīng)該會(huì)增加對(duì)注解模式的支持。
事件回調(diào)機(jī)制主要是針對(duì)服務(wù)調(diào)用方,也就是消費(fèi)者的,配置方式也很簡(jiǎn)單,只需要在dubbo的消費(fèi)者xml中加入dubbo:method,并指定oninvoke、onreturn、onthrow方法:
<bean id ="demoCallback" class = "org.apache.dubbo.callback.implicit.NotifyImpl" />
<dubbo:reference id="demoService" interface="org.apache.dubbo.callback.implicit.IDemoService" version="1.0.0" group="cn" >
<dubbo:method name="get" async="true" onreturn = "demoCallback.onreturn" onthrow="demoCallback.onthrow" oninvoke = "demoCallback.oninvoke"/>
</dubbo:reference>
其中,demoCallback是我們自己定義的回調(diào)接口,具體實(shí)現(xiàn)如下:
class NotifyImpl implements Notify {
public Map<Integer, Person> ret = new HashMap<Integer, Person>();
public Map<Integer, Throwable> errors = new HashMap<Integer, Throwable>();
public void onreturn(Person msg, Integer id) {
System.out.println("onreturn:" + msg);
ret.put(id, msg);
}
public void onthrow(Throwable ex, Integer id) {
errors.put(id, ex);
}
public void oninvoke(Integer id) {
System.out.print(id)
}
}
我的錯(cuò)誤
下面是我的調(diào)用方代碼:
@DubboReference(version = "1.0", interfaceName = "demoService", interfaceClass = DemoService.class,
loadbalance = "roundrobin", methods = {@Method(name = "sayHello", oninvoke = "callBackDemoService.oninvoke")})
private DemoService demoService;
@RequestMapping("/test")
public Object demo() {
String hello = demoService.sayHello("world");
System.out.println(hello);
return hello;
}
回調(diào)接口實(shí)現(xiàn):
@Service("callBackDemoService")
public class CallBackDemoServiceImpl implements CallBackDemoSevice {
@Override
public void oninvoke(String name) {
System.out.println("oninvoke name =" + name);
}
@Override
public String onreturn(String response) {
System.out.println("onreturn response =" + response);
return "onreturn" + response;
}
@Override
public void onthrow(Throwable t) {
System.err.println("onthrow Throwable =" + t);
}
}
我通過(guò)注解指定了oninvoke的方法,但是在調(diào)用服務(wù)提供者的時(shí)候報(bào)錯(cuò)了,控制臺(tái)錯(cuò)誤提示如下:

然后我debug發(fā)現(xiàn),是因?yàn)?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">AsyncMethodInfo的oninvokeMethod方法為空導(dǎo)致的

在搜集錯(cuò)誤的過(guò)程中,我發(fā)現(xiàn)了下面這些很有用的知識(shí)點(diǎn),各位小伙伴可以看下:
oninvoke方法
必須具有與真實(shí)的被調(diào)用方法 sayHello相同的入?yún)⒘斜恚豪纾?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">oninvoke(String name)
onreturn方法
至少要有一個(gè)入?yún)⑶业谝粋€(gè)入?yún)⒈仨毰c getUserName的返回類(lèi)型相同,接收返回結(jié)果:例如,onReturnWithoutParam(String result);可以有多個(gè)參數(shù),多個(gè)參數(shù)的情況下,第一個(gè)后邊的所有參數(shù)都是用來(lái)接收 getUserName入?yún)⒌模豪纾?onreturn(String result, String name)
onthrow方法
至少要有一個(gè)入?yún)⑶业谝粋€(gè)入?yún)㈩?lèi)型為
Throwable或其子類(lèi),接收返回結(jié)果;例如,onthrow(Throwable ex);可以有多個(gè)參數(shù),多個(gè)參數(shù)的情況下,第一個(gè)后邊的所有參數(shù)都是用來(lái)接收
getUserName入?yún)⒌模豪纾?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">onthrow(Throwable ex, String name);如果是
consumer在調(diào)用provider的過(guò)程中,出現(xiàn)異常時(shí)不會(huì)走onthrow方法的,onthrow方法只會(huì)在provider返回的RpcResult中含有Exception對(duì)象時(shí),才會(huì)執(zhí)行。(dubbo中下層服務(wù)的Exception會(huì)被放在響應(yīng)RpcResult的exception對(duì)象中傳遞給上層服務(wù))
好吧,我妥協(xié)了
最后,我還是用xml的方式測(cè)試了,確實(shí)是可以回調(diào)的,dubbo消費(fèi)者xml配置如下:
<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder/>
<bean id ="demoCallback" class = "io.github.syske.demo.service.consumer.callback.impl.CallBackDemoServiceImpl" />
<dubbo:reference id="demoService" interface="io.github.syske.common.facade.DemoService" version="1.0" >
<dubbo:method name="sayHello" async="true" oninvoke="demoCallback.oninvoke" onreturn = "demoCallback.onreturn" onthrow="demoCallback.onthrow" />
</dubbo:reference>
<dubbo:application name="callback-consumer"/>
<dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>
</beans>
修改主程序,刪除原有注解:
@SpringBootApplication
//@EnableDubbo
public class DemoConsumerApplication {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
context.start();
DemoService demoService = (DemoService) context.getBean("demoService");
String hello = demoService.sayHello("world");
System.out.println(hello);
}
}
然后啟動(dòng)運(yùn)行,從控制臺(tái)看出,我們的回調(diào)方法都被執(zhí)行了:


根據(jù)實(shí)現(xiàn)機(jī)制,我推測(cè)回調(diào)方法是基于動(dòng)態(tài)代理實(shí)現(xiàn)的,關(guān)于動(dòng)態(tài)代理的應(yīng)用我們前面在分享手寫(xiě)rpc的時(shí)候有講過(guò),最常用的場(chǎng)景之一就是AOP,據(jù)說(shuō)Spring、Struts等框架就是通過(guò)動(dòng)態(tài)代理技術(shù)來(lái)實(shí)現(xiàn)日志、切面編程這些操作的。
柳暗花明又一村
用xml的方式測(cè)試完成后,我又看了下關(guān)于那個(gè)問(wèn)題官方給出的回復(fù),發(fā)現(xiàn)在2021.5.12日這個(gè)問(wèn)題已經(jīng)被修復(fù)了,說(shuō)明之后的版本已經(jīng)可以通過(guò)注解方式進(jìn)行事件通知回調(diào)了,而且我親測(cè)在2.7.12之后的版本就已經(jīng)可以了:


感覺(jué)我可真是個(gè)小機(jī)靈鬼,這都讓我發(fā)現(xiàn)了??,當(dāng)然我也停享受這個(gè)柳暗花明又一村的感覺(jué)的,這種學(xué)習(xí)方式感覺(jué)挺好,就像發(fā)現(xiàn)新大陸一樣……
總結(jié)
目前來(lái)看,事件通知的最大應(yīng)用場(chǎng)景就是AOP,但是缺點(diǎn)是,每個(gè)方法都需要單獨(dú)指定(@Method(name = "sayHello", oninvoke = "callBackDemoService.oninvoke")中的name是必填項(xiàng),而且不支持正則表達(dá)式),這就很不友好了,但是有總比沒(méi)有強(qiáng),你說(shuō)呢?
好了,今天的內(nèi)容就到這里吧,有興趣的小伙伴自己動(dòng)手試下哦!
項(xiàng)目源碼地址如下:
https://github.com/Syske/learning-dome-code/tree/dev/spring-boot-dubbo-demo
- END -