Apache Dubbo编程指南系列之泛化引用、Protobuf、泛化服务、回声测试、上下文、隐士参数、参数回调、事件通知

Generic Reference

实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。泛化接口调用方式主要用于客户端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过 GenericService 调用所有服务实现。基本类型以及Date,List,Map等不需要转换,直接调用 。

<dubbo:reference id="userService"  
                 interface="com.baizhi.service.IUserService" group="*" generic="true" >
    <dubbo:method name="save" merger="false"/>
</dubbo:reference>
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application.xml");
ctx.start();

GenericService userService = (GenericService) ctx.getBean("userService");
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("class","com.baizhi.entities.User");
params.put("id",1);
params.put("name","jiangzz");
params.put("birthDay",new Date());
//参数名 参数类型 参数
userService.$invoke("save",new String[]{"com.baizhi.entities.User"},new Object[]{params});

ctx.stop();

Dubbo的参数验证无法验证泛华引用类型。

Java API调用

ReferenceConfig<GenericService> referenceConfig=new ReferenceConfig<>();
referenceConfig.setInterface("com.baizhi.service.IUserService");
referenceConfig.setGroup("*");
referenceConfig.setGeneric(true);

HashMap<String, Object> params = new HashMap<String, Object>();
params.put("class","com.baizhi.entities.User");
params.put("id",1);
params.put("name","jiangzz");
params.put("birthDay",new Date());
params.put("sex",false);
referenceConfig.get().$invoke("save",new String[]{"com.baizhi.entities.User"},new Object[]{params});

Protobuf

当前 Dubbo 的服务定义和具体的编程语言绑定,没有提供一种语言中立的服务描述格式,比如 Java 就是定义 Interface 接口,到了其他语言又得重新以另外的格式定义一遍。 2.7.5 版本通过支持 Protobuf IDL 实现了语言中立的服务定义。日后,不论我们使用什么语言版本来开发 Dubbo 服务,都可以直接使用 IDL 定义如下服务。

①在项目java平级的目录下创建proto文件夹,然后在该文件夹下创建DemoService.proto文本文件

syntax = "proto3";

option java_package = "com.baizhi.proto";
option java_multiple_files = true;
option java_outer_classname = "DemoServiceProto";
option optimize_for = SPEED;

// The demo service definition.
service DemoService {
  rpc query (Request) returns (Response){}
}

// The request message containing the user's name.
message Request {
  string name = 1;
}

// The response message containing the greetings
message Response {
  string message = 1;
}

②在消费端和服务端分别引入以下依赖

<!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-serialization-protobuf -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-serialization-protobuf</artifactId>
    <version>2.7.8</version>
</dependency>
<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>1.6.1</version>
        </extension>
    </extensions>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.7.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>
            <version>0.5.1</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
                <outputDirectory>src/main/java</outputDirectory>
                <clearOutputDirectory>false</clearOutputDirectory>
                <protocPlugins>
                    <protocPlugin>
                        <id>dubbo</id>
                        <groupId>org.apache.dubbo</groupId>
                        <artifactId>dubbo-compiler</artifactId>
                        <version>0.0.1</version>
                        <mainClass>org.apache.dubbo.gen.dubbo.DubboGenerator</mainClass>
                    </protocPlugin>
                </protocPlugins>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

③执行protobuf:compile指令分别在消费端和服务提供端生成相关的代码

④在服务端编写DemoService的实现

public class ProtoBufDemoService implements DemoServiceDubbo.IDemoService {
    @Override
    public Response query(Request request) {
        System.out.println("接收来自客户端消息:"+request.getName());
        Response.Builder builder = Response.newBuilder();
        builder.setMessage("服务端响应");
        return builder.build();
    }

    @Override
    public CompletableFuture<Response> queryAsync(Request request) {
        return CompletableFuture.completedFuture(query(request));
    }
}

⑤配置该Bean,并且使用Dubbo暴露该服务

<bean id="protoBufDemoService" class="com.baizhi.service.impl.ProtoBufDemoService"/>
<dubbo:service interface="com.baizhi.proto.DemoServiceDubbo$IDemoService" ref="protoBufDemoService"/>

注意这里的serialization属性配置的是元素的序列化方式,这里我们必须选择protobuf即可

⑥配置服务引用

<dubbo:reference id="demoService" interface="com.baizhi.proto.DemoServiceDubbo$IDemoService"/>

⑦测试调用远程服务

DemoServiceDubbo.IDemoService demoService = (DemoServiceDubbo.IDemoService) ctx.getBean("demoService");
Request req = Request.newBuilder().setName("测试用户").build();
Response response = demoService.query(req);

System.out.println(response.getMessage());

Generic Service

泛接口实现方式主要用于服务器端没有 API 接口及模型类元的情况,参数及返回值中的所有 POJO 均用 Map 表示,通常用于框架集成,比如:实现一个通用的远程服务 Mock 框架,可通过实现 GenericService 接口处理所有服务请求。

①编写泛化服务bin注册

public class UserDefineGenericService implements GenericService {

    @Override
    public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
        if ("sayHello".equals(method)) {
            return "Welcome " + args[0];
        }else{
            return null;
        }
    }
}

<bean id="genericService" class="com.baizhi.service.impl.UserDefineGenericService"/>
<dubbo:service interface="com.baizhi.service.impl.XxxService" ref="genericService"/>

②泛化服务调用

<dubbo:reference   id="xxxService" generic="true" interface="com.baizhi.service.impl.XxxService"/>
GenericService genericSerivce = (GenericService) ctx.getBean("xxxService");
Object result=genericSerivce.$invoke("sayHello",new String[]{"java.lang.String"},new Object[]{"张三"});
System.out.println(result);

Echo Test

通过回声测试检测 Dubbo 服务是否可用,回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控。所有服务自动实现 EchoService 接口,只需将任意服务引用强制转型为 EchoService,即可使用。

<dubbo:reference id="userService"  interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 group="*"
                 validation="true">

 <dubbo:method name="save"  timeout="5000" loadbalance="consistenthash"  merger="true" validation="true" />
 <dubbo:method name="queryAll"  timeout="5000" loadbalance="consistenthash"  merger="true" cache="jcache"  />
 <dubbo:method name="queryUserById"  timeout="5000" loadbalance="consistenthash"  merger=".mergerUser"  />
</dubbo:reference>
EchoService echoService = (EchoService) ctx.getBean("userService");
Object result = echoService.$echo("hello");
System.out.println(result);

这里需要注意仅仅只有非泛化的引用才能使用回声测试。

Context

通过上下文存放当前调用过程中所需的环境信息,上下文中存放的是当前调用过程中所需的环境信息。所有配置信息都将转换为 URL 的参数,参见 schema 配置参考手册 中的对应URL参数一列。RpcContext 是一个 Thread Local的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。比如:A 调 B,B 再调 C,则 B 机器上,在 B 调 C 之前,RpcContext 记录的是 A 调 B 的信息,在 B 调 C 之后,RpcContext 记录的是 B 调 C 的信息。

①服务消费方

// 远程调用
xxxService.xxx();
// 本端是否为消费端,这里会返回true
boolean isConsumerSide = RpcContext.getContext().isConsumerSide();
// 获取最后一次调用的提供方IP地址
String serverIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();

②服务提供方

// 本端是否为提供端,这里会返回true
boolean isProviderSide = RpcContext.getContext().isProviderSide();
// 获取调用方IP地址
String clientIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
// 此时本端变成消费端,这里会返回false
boolean isProviderSide = RpcContext.getContext().isProviderSide();

Implicit parameters

通过 Dubbo 中的 Attachment 在服务消费方和提供方之间隐式传递参数,可以通过 RpcContext 上的 setAttachmentgetAttachment 在服务消费方和提供方之间进行参数的隐式传递。

path, group, version, dubbo, token, timeout 几个 key 是保留字段,请使用其它值。
在这里插入图片描述

①在服务消费方端设置隐式参数

RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
xxxService.xxx(); // 远程调用
// ...

②在服务提供方端获取隐式参数

public class XxxServiceImpl implements XxxService {
    public void xxx() {
        // 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用
        String index = RpcContext.getContext().getAttachment("index"); 
    }
}

Callback parameter

通过参数回调从服务器端调用客户端逻辑,参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。

public interface IUserService {
    ...
    public void saveUserWithCallBack(User user ,UserCallback userCallback);
}
public interface UserCallback {
    public User  modifyUser(User user);
}

①服务端实现

public class UserServiceImp01 implements IUserService {
   //...

    public void saveUserWithCallBack(User user, UserCallback userCallback) {
        System.out.println("saveUserWithCallBack:"+user);
        user.setName("zhangsan");
        user=userCallback.modifyUser(user);
        System.out.println(user);
    }
}
<dubbo:service interface="com.baizhi.service.IUserService" ref="userService01"
               protocol="dubbo_protocol"
               application="app1"
               registry="zk_registry"
               retries="2"
               weight="100"
               group="g1"
               loadbalance="random">
    ...
    <!--配置回调参数-->
    <dubbo:method name="saveUserWithCallBack" >
        <dubbo:argument index="1" callback="true"/>
    </dubbo:method>
</dubbo:service>

②消费端实现

IUserService userSerivce = ctx.getBean("userService", IUserService.class);
userSerivce.saveUserWithCallBack(new User(), new UserCallback() {
    @Override
    public User modifyUser(User user) {
        System.out.println("服务端回调");
        user.setName(user.getName()+"_客户端追加!");
        return user;
    }
});

可以简单的理解为当设置为回调参数的时候,相当于服务端那边创建一个代理直接回调客户端的代码,继而实现客户端向服务端传递代码逻辑!

Event Notification

在调用之前、调用之后、出现异常时的时间通知,在调用之前、调用之后、出现异常时,会触发 oninvokeonreturnonthrow 三个事件,可以配置当事件发生时,通知哪个类的哪个方法。

public interface Notify {
    public void onreturn(Object msg, Integer id);
    public void onthrow(Throwable ex, Integer id);
    public void oninvoke(Integer id);
}
public class UserNotify implements Notify {
    public void onreturn(Object msg, Integer id) {
        System.out.println("onreturn"+"\t"+msg+"\t"+id);
    }
    public void onthrow(Throwable ex, Integer id) {
        System.out.println("onthrow:"+ex+"\t"+id);
    }
    public void oninvoke(Integer id) {
        System.out.println("oninvoke:"+id);
    }
}

①消费者引用消费服务

<dubbo:reference id="userService"  interface="com.baizhi.service.IUserService"
                 registry="zk_registry"
                 application="app1"
                 timeout="1000"
                 retries="4"
                 cluster="failover"
                 loadbalance="consistenthash"
                 group="g1"
                 validation="true">
    <dubbo:method name="queryUserById"  
                  onreturn="notify.onreturn" 
                  onthrow="notify.onthrow" 
                  oninvoke="notify.oninvoke" />
</dubbo:reference>

这里面有个bug,如果消费者这边存在同时merger多个分组的结果,此时系统不会做时间通知。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值