22.本地存根
消费者通过创建实现一个服务接口的实例,可以在执行远程调用前拿到远程调用的代理实例,进而可以在远程调用前、后添加一些操作,在出现异常后进行一些容错处理。
这个使用场景,可以调用前作数据参数校验、做ThreadLocal缓存(这个线程操作多次调用这个服务,而且结果是一样的,就可以缓存起来,第二次就不用再远程调用)、出错后如何处理等等;其实就是相当于一个AOP一样的功能
如
服务接口:
public interface User {
String getUserInfoByName(String name);
}
提供者和平常一样正常实现接口并注册能力到注册中心:
消费者创建一个服务接口实现类:
public class UserSub implements User {
private final User user;
/**必须提供一个传服务接口的构造器,最终传入的是代理对象,用于调用远程服务*/
public UserSub(User user){
this.user = user;
}
public String getUserInfoByName(String name) {
try {
//远程调用之前
System.out.println("远程调用之前");
//远程调用
String userInfoByName = this.user.getUserInfoByName(name);
//远程调用之后
System.out.println("远程调用之后"+userInfoByName);
return userInfoByName;
}catch (Exception e) {
return null;
}
}
}
配置消费者:(stub指向刚才的类)
使用:和平常一样
User user = (User) context.getBean("user");
System.out.println(user.getUserInfoByName("啊哈"));
结果:(提供者是直接返回name)
------------
远程调用之前
远程调用之后啊哈
啊哈(远程调用结果)
------------
21.事件通知
用于消费者reference下的method 属性
,
oninvoke:调用服务之前;
onreturn:调用方法之后;
onthrow:出异常后;
它们的值是spring容器中某个bean的名称.方法名
如:
要注意的是TestNotify的三个方法参数问题
先看服务接口:
/**两个参数*/
public interface CallBackService extends Serializable{
String addListener(String name, CallBackListener callBackListener);
}
public class TestNotify {
/**oninvoke方法的参数必须与服务接口一致,包括顺序*/
public void oninvoke(String name, CallBackListener listener){
System.out.println("-------oninvoke-----------");
}
/**onreturn方法的第一个参数是服务接口的返回类型,服务接口的参数要么全无*/
public void onreturn(String result){
System.out.println("----------onreturn--------");
}
/**onreturn方法的第一个参数是服务接口的返回类型,服务接口的参数要么全有,但顺序也要一致*/
public void onreturn2(String result,String name, CallBackListener listener){
System.out.println("----------onreturn--------");
}
/**onthrow方法的第一个参数是异常类型,服务接口的参数要么全无*/
public void onthrow(Throwable ex) {
System.out.println("--------onthrow----------");
}
/**onthrow方法的第一个参数是异常类型,服务接口的参数要么全有,但顺序也要一致*/
public void onthrow2(Throwable ex,String name, CallBackListener listener) {
System.out.println("--------onthrow----------");
}
}
值得注意的是:假如配置onthrow 方法,也配置了oninvoke或onreturn,如果oninvoke或onreturn的参数不对(或这两个方法里面报异常),将会把oninvoke或onreturn转为调用onthrow,这样就有可能出现明明是服务调用正常却跑进了onthrow
比如:
/**oninvoke方法的参数必须与服务接口一致,包括顺序*/
public void oninvoke(String name, CallBackListener listener){
System.out.println("-------oninvoke-----------"+1/0);
}
结果是:
-------------
回调:222(服务调用前逻辑)
--------onthrow----------(服务调用前通知,明明是服务正常调用,去跑进了onthrow,却又没发现代码有抛异常)
回调:CallBackService:CallBackServiceImpl(服务调用)
--------onthrow----------(服务调用后通知)
-------------
20.参数回调
定义一些服务接口,参数传一个回调监听器,这样消费者调用服务是,可以在服务端的回调客服端的代码,比如在提供者根据参数,符合条件才执行回调;
补充:就是提供者某个入参为一个抽象的一个接口,而具体的实现由消费者实现,这样把消费者写的代码逻可以在服务端执行,也就是实现一种RPC的广义多态
原理:服务端检查到某个服务接口的某个方法的某个参数是回调的,会通过注册通行,在消费者暴露一个接口给服务端调用( 需要在provider中配置回调参数,这些配置信息会通过registry传递到consumer中 )
例子:
/**正常的一个服务接口,有个回调参数callBackListener*/
public interface CallBackService extends Serializable{
void addListener(String name, CallBackListener callBackListener);
}
/**回调参数接口*/
public interface CallBackListener extends Serializable {
void listner(String name);
}
提供者:
public class CallBackServiceImpl implements CallBackService {
public void addListener(String name, CallBackListener callBackListener) {
name += "CallBackServiceImpl";
callBackListener.listner(name);//调用回调
}
}
提供者配置回调参数(指定哪个方法第几个参数为callBack=true):
消费者正常配置:
消费服务
CallBackService sbs = (CallBackService) context.getBean("sbs");
CallBackListener listener = new CallBackListener() {
public void listner(String name) {
System.out.println("回调:"+name);//2
}
};
listener.listner("222");//3
sbs.addListener("CallBackService:", listener);//1
结果
---------------
回调:222
回调:CallBackService:CallBackServiceImpl
---------------
注意: 提供者执行了这个代码 callBackListener.listner(name);//调用回调,不管后面有没有报错、超时,消费这个的回调函数一样被调用
如果把提供者改为:
提供者:
public void addListener(String name, CallBackListener callBackListener) {
name += "CallBackServiceImpl";
callBackListener.listner(name);
int a =1/0;
}
消费者不变,结果为:
--------------
回调:222
回调:CallBackService:CallBackServiceImpl
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.ned.rpc.CallBackServiceImpl.addListener(CallBackServiceImpl.java:18)
at com.alibaba.dubbo.common.bytecode.Wrapper5.invokeMethod(Wrapper5.java)
at com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:47)
at com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:76)
……
--------------
19.异步调用
在reference获取其method上使用async=true,当调用方法发出去后,立即响应null,通过Future来获取异步的响应;
提供者:
public String getUserInfoByName(String name) {
String test = RpcContext.getContext().getAttachment("test");
return userService.getUserInfoByName(test+"getUserInfoByName"+name);
}
public String getUserInfoByName2(String name) {
String test = RpcContext.getContext().getAttachment("test");
return userService.getUserInfoByName(test+"getUserInfoByName2"+name);
}
消费者:
调用:
User2 user2 = (User2) context.getBean("user2");
RpcContext.getContext().setAttachment("test", "测试Rpcontext");
System.out.println(user2.getUserInfoByName("555"));//1
System.out.println(user2.getUserInfoByName2("555"));//2
Future future = RpcContext.getContext().getFuture();
System.out.println(future.get());
结果:(前两个null是异步立即返回null,第三个null是提供者getUserInfoByName2中获取隐形参数)
----
null
null
nullgetUserInfoByName2555(这个其实是2注释的远程调用的结果,同时RpcContext中第一次远程调用的结果已经配情况)
----
========================================================================
再调用:
User2 user2 = (User2) context.getBean("user2");
RpcContext.getContext().setAttachment("test", "测试Rpcontext");
System.out.println(user2.getUserInfoByName("555"));
Future future = RpcContext.getContext().getFuture();//两行顺序换了
System.out.println(user2.getUserInfoByName2("555"));
System.out.println(future.get());
结果:
----
null
null
测试RpcontextgetUserInfoByName555(这一次可以获取到RPCContext中的隐性参数)
----
18.上下文信息
隐式参数每一次远程调用后都会被清空
17.回声测试
消费者从容器中获取服务bean实例强转为 EchoService这个类型,调用其唯一方法Object $echo(Object var1);
使用(通过$Echo方法发出去什么内容,响应回来什么内容,这个就可以测试服务提供是否正常):
User3 user = (User3) context.getBean("user");//这个是一个服务实例
EchoService echoService = (EchoService) user;//转为EchoService类型
Object ok = echoService.$echo("响应我回来就成功了");
System.out.println(ok);
输出:
----
响应我回来就成功了
----
package com.alibaba.dubbo.rpc.service;
public interface EchoService {
Object $echo(Object var1);
}
16.泛化调用
提供者跟其他一样,不同在于消费者。消费者reference标签上使用generic="true" 的属性,而interface的接口在消费者是不存在的。
A.参数为基本简单类型
消费者配置:
a.给reference配置id;
b.给reference配置interface,注意这个com.ned.generic.GenericApi在是接口的全称,但这接口在消费者是不存在的;
c.给reference配置generic为true,标识泛化调用
使用:
a.从容器中获取一个bean,名称为上面配置的Id属性,强转为GenericService
GenericService genericService = (GenericService) context.getBean("genericService");
b.调用genericService唯一的一个方法$invoke();
Object testGeneric = genericService.$invoke("testGeneric", new String[]{"java.lang.String"}, new Object[]{"啊哈"});
结果与正常的调用一样
public interface GenericService {
/**
* 泛化调用
*
* @param method 方法名,如:findPerson,如果有重载方法,需带上参数列表,如:findPerson(java.lang.String)
* @param parameterTypes 参数类型
* @param args 参数列表
* @return 返回值
* @throws Throwable 方法抛出的异常
*/
Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
}
B.参数为对象类型
提供者有个方法如下:
person对象
消费者使用:
a.从容器中获取一个bean,名称为上面配置的Id属性,强转为GenericService
GenericService genericService = (GenericService) context.getBean("genericService");
b.调用genericService唯一的一个方法$invoke();
对象类型的参数用Map封装,如果返回结果也是对象类型,也会被转成map
Map paramMap = new HashMap();
paramMap.put("class", "com.ned.entity.Person");
paramMap.put("name","啊哈");
paramMap.put("password","123456");
Map resultMap = (Map) genericService.$invoke("findPerson", new String[]{"com.ned.entity.Person"}, new Object[]{paramMap});
System.out.println(resultMap);
服务端泛化的实现
服务接口不在提供者模块(也就是提供者是没有接口的情况,与泛化调用相反)
有个接口在消费者模块中(注意:在提供者模块是没有这个接口的)
public interface ConsumerGenericApi {
String serviceGenericTest(String name);
}
a.服务端实现GenericService接口:
public class ServiceGenericImpl implements GenericService {
public Object $invoke(String s, String[] strings, Object[] objects) throws GenericException {
return "OK";
}
}
b.配置服务端提供者:
(com.ned.generic.ServiceGenericImpl,就是上面的实现类;interface="com.ned.consumer.generic.ConsumerGenericApi" 这是接口的全称,但不在提供这个的模块中)
c.配置消费者:
(与正常的消费者一样)
d.消费者从容器中获取接口实例consumerGenericApi(c步骤中的id),直接调用方法:
(也就是接口在消费者,实现在提供者,因此serviceGenericTest方法调用,其实走了消费者的$invoke方法)
ConsumerGenericApi consumerGenericApi = (ConsumerGenericApi) context.getBean("consumerGenericApi");
String t = consumerGenericApi.serviceGenericTest("t");
System.out.println(t);
结果:(a步骤中return的:OK)
-----
OK
-----
15.分组聚合
merger="true" 布尔型,用在reference 或其的method上,true :把指定的组结果聚合,fasle:处哪个不聚合
*注意的是,服务的返回类型是有要求的,在com.alibaba.dubbo.rpc.cluster.merger这个包下的所有类型
也就是数组,list,map,set
14.多版本
消费者与提供者使用相同的version
13. 服务分组
使用场景:消费者的group,可以统一,从而实现开发/测试环境的快速切换调用不同的能力如:
service端,同一个接口的多种实现
消费者
(如果所有提供者都配了group,消费者一定要添加group属性,可以是具体,可以是*,如果提供者部分配置group,消费者有不配那么消费者只会使用不配group的提供的接口)
12.多协议
在reference、service上通过protocol字段设置对于的protocol的名称即可(service上可以设置多个
com.caucho
hessian
4.0.7
org.mortbay.jetty
jetty
6.1.26
b.设置提供者协议
c.设置消费者协议(不用指定sever)
10. 静态服务
应用部署后,服务是禁用状态,需要人工启动,断线后,也不会清除,要人工删除
标签
9.只注册,不订阅别人服务
a.只是提供服务,不用注册中心订阅别的服务(不依赖别的服务);
b.有两个注册中心R1与R2,服务S1在R1上注册了,不能在R2上注册,但是有应用A要使用服务S1并且应用的注册A的中心是由R1与R2集群的,这样会导致一些请求到R2上会没找到服务S1;因此应用A在配置注册中心集群时就必须对R2进行只注册(也就是所有依赖的服务都从R1中获取),但又不能把R2直接去掉,因为应用A要注册一下服务到R2上给别的应用使用;
8.只订阅,不注册自己服务
开发者要依赖使用通过注册中心上的服务,但是又不想把自己正在开发的服务注册到注册中心(原因正在开发的功能未完善,注册到注册中心可能会影响别调用未完善的服务)
使用标签 ,在注册标识reigistry设置为reigster="false" 就不会本服务注册注册中心了
7.直连提供者
三种方式:JVM参数、通过JVM参数指定配置文件、标签(前面的会覆盖后面的)
jvm参数:
如下图所示,消费者对于api.User这个服务是不通注册中心,而是直接连接消费者
配置文件的方式(用于多个接口要处理时,可以配置到单独的文件上):
a.创建配置文件放到${user_home}/ dubbo-resolve.properties(注意不要改名)
${user.home}指的是当前操作系统用户目录,如 Win7系统 Administrator的用户目录就是 C:\Users\Administrator
b.编辑配置文件映射
安装上图还是会执行20889端口的原因是因为jvm的配置会覆盖配置文件的配置,把-Dapi.User=dubbo://localhost:20889去除,就会直连20888
标签reference上
标签笔记简单,直接在url中添加就可以,但是要注意的是看看会不会陪前面两种覆盖
6.线程模型(超链接文章详细说明)
使用在protocal标签上
Dispatcher
all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。(缺省)
对应相关