本地调用:http://dubbo.apache.org/en-us/docs/user/demos/local-call.html
参数回调:http://dubbo.apache.org/en-us/docs/user/demos/callback-parameter.html
事件通知:http://dubbo.apache.org/zh-cn/docs/user/demos/events-notify.html
如果Consumer与Provider部署在一台主机上,共用一个JVM,那么当Consumer调用Provider时就没有必要经过网络栈,直接调用即可,不需要通过网络,便是Dubbo的其它Filtter正常生效。
对于Provider端而言,从2.2.0开始默认自动支持本地调用,无需任务特殊配置。值得注意的是,如果只打算让Provider提供的服务支持本地调用,可以把服务的protocol设置成injvm一个协议。这样,被配置的服务将不用支持远程调用,服务在运行时也不会开端口。
对于Consumer端而言,当调用某个服务时,如果它在本地已经暴露,则默认直接调用,这个是从2.2.0开始后的默认行为。如果想关掉此行为,则如下配置:
有点鸡肋的功能,为服务本身就是要将功能打散然后分布到系统中的多个节点上,为什么会分布在一个节点上呢?另个就算不走injvm,走其它协议,开销能有多大呢?不能指着把服务部署在相同节点上节省网络流量吧。
抛开Dubbo,在普通的开发中,当调用一个方法时,我们可能在参数中给方法传递一个引用,然后方法在执行的过程中调用引中的一个方法,这就是普通的回调。
Dubbo也有类似功能,Dubbo 将基于长连接生成反向代理,Consumer端临时允当Provider端,Provider端调用的接口,真正的逻辑将在Consumer端执行。
服务端接口示例,CallbackService.java:
package com.callback;
public interface CallbackService {
void addListener(String key, CallbackListener listener);
}
后边一个参数是接口,下边是参数接口定义CallbackListener.java文件:
package com.callback;
public interface CallbackListener {
void changed(String msg);
}
这个接口要在Consumer端实现。
服务端接口实现:
package com.callback.impl;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.callback.CallbackListener;
import com.callback.CallbackService;
public class CallbackServiceImpl implements CallbackService {
private final Map listeners = new ConcurrentHashMap();
public CallbackServiceImpl() {
Thread t = new Thread(new Runnable() {
public void run() {
while(true) {
try {
for(Map.Entry entry : listeners.entrySet()){
try {
entry.getValue().changed(getChanged(entry.getKey()));
} catch (Throwable t) {
listeners.remove(entry.getKey());
}
}
Thread.sleep(5000); // 定时触发变更通知
} catch (Throwable t) { // 防御容错
t.printStackTrace();
}
}
}
});
t.setDaemon(true);
t.start();
}
public void addListener(String key, CallbackListener listener) {
listeners.put(key, listener);
listener.changed(getChanged(key)); // 发送变更通知
}
private String getChanged(String key) {
return "Changed: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}
在上边的实现中,在构造方法中启动了一个线程,里边是一个无限循环,按固定间隔调用从Consumer端注册进来的回调。
服务端配置:
需要明确指定那个参数是用来回调的。
Consumer端配置,一切如常:
Consumer端代码:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:consumer.xml");
context.start();
CallbackService callbackService = (CallbackService) context.getBean("callbackService");
callbackService.addListener("foo.bar", new CallbackListener(){
public void changed(String msg) {
System.out.println("callback1:" + msg);
}
});
需要为回调的参数指定一个实例。
对于一次远程方法调用,有oninvoke
、onreturn
、onthrow三个事件,分别为调用之前、返回之后,抛出异常三个事件。在Consumer端,可以为三个事件指定事件处理方法。
服务端接口:
interface IDemoService {
public Person get(int id);
}
服务端实现:
class NormalDemoService implements IDemoService {
public Person get(int id) {
return new Person(id, "charles`son", 4);
}
}
服务端配置:
没有什么特别的地方。
Consumer端的处理事件通知的接口:
interface Notify {
public void onreturn(Person msg, Integer id);
public void onthrow(Throwable ex, Integer id);
}
方法中,第一个参数应该是方法的返回值,其它是方法的参数。
然后Consumer端实现这个接口:
class NotifyImpl implements Notify {
public Map ret = new HashMap();
public Map errors = new HashMap();
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);
}
}
服务端配置:
配置中async与onreturn、onthrow是一对,组合不同,功能也不同:
async=true onreturn="xxx"
async=false onreturn="xxx"
async=true
async=false
Consumer端测试代码:
IDemoService demoService = (IDemoService) context.getBean("demoService");
NofifyImpl notify = (NofifyImpl) context.getBean("demoCallback");
int requestId = 2;
Person ret = demoService.get(requestId);
Assert.assertEquals(null, ret);
//for Test:只是用来说明callback正常被调用,业务具体实现自行决定.
for (int i = 0; i < 10; i++) {
if (!notify.ret.containsKey(requestId)) {
Thread.sleep(200);
} else {
break;
}
}
Assert.assertEquals(requestId, notify.ret.get(requestId).getId());
当前线程正在执行for循环,事件处理器中的代码应该是在Consumer端的IOThread中执行,可能涉及到线程同步的问题。