我是靠谱客的博主 真实衬衫,最近开发中收集的这篇文章主要介绍Dubbo笔记 ⑲ :隐式参数传递一、前言二、简易demo三、源码分析四、总结,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • 一、前言
  • 二、简易demo
  • 三、源码分析
    • 1. AbstractClusterInvoker
    • 2. ConsumerContextFilter
    • 3. ContextFilter
  • 四、总结
    • 1. 个人的疑问

一、前言

本系列为个人Dubbo学习笔记,内容基于《深度剖析Apache Dubbo 核心技术内幕》, 过程参考官方源码分析文章,仅用于个人笔记记录。本文分析基于Dubbo2.7.0版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。


Dubbo提供了隐式参数传递的功能,即服务调用方可以通过RpcContext.getContext().setAttachment() 方法设置附加属性键值对,然后设置的键值对可以在服务提供方服务方法内获取。

二、简易demo

@Service
public class MainSimpleDemoServiceImpl implements SimpleDemoService {
    @Override
    public String sayHello(String msg) {
    	// 这里会将上下文的参数获取到并返回
        Object context = RpcContext.getContext().getAttachment("context");
        return "MainSimpleDemoServiceImpl : " + msg + " context = " + context;
    }
}

// 消费者。设置参数 context =  SimpleConsumer
public class SimpleConsumer {

    public static void main(String[] args) throws InterruptedException {
        ReferenceConfig<SimpleDemoService> referenceConfig = DubboUtil.referenceConfig("dubbo-consumer", SimpleDemoService.class);
        referenceConfig.setMonitor("http://localhost:8080");
        SimpleDemoService demoService = referenceConfig.get();
        // 消费者设置隐式参数 context = SimpleConsumer,提供者可以获取到该参数
        RpcContext.getContext().setAttachment("context", "SimpleConsumer");
        // 输出 MainSimpleDemoServiceImpl : SimpleConsumer context = SimpleConsumer
        System.out.println(demoService.sayHello("SimpleConsumer"));
    }
    
}

// 服务提供者
public class SimpleProvider {
    public static void main(String[] args) throws IOException {
        ServiceConfig<SimpleDemoService> serviceConfig = DubboUtil.serviceConfig("dubbo-provider", SimpleDemoService.class, new MainSimpleDemoServiceImpl());
        serviceConfig.export();
        System.out.println("service is start");
        System.in.read();
    }
}

这里可以看到,消费者在调用sayHello 前,在上下文中通过 RpcContext.getContext().setAttachment("context", "SimpleConsumer"); 保存了隐式参数 context = SimpleConsumer。在提供者端可以通过 RpcContext.getContext().getAttachment("context"); 获取到隐式参数的值。

不过需要注意的是,上下文参数是一次性的,即设置一次参数只能获取一次。

三、源码分析

Dubbo 隐式参数实现依赖于 ConsumerContextFilter & ContextFilter 完成,下面我们来看具体逻辑:

1. AbstractClusterInvoker

消费者发起调用时,会在 AbstractClusterInvoker#invoke 方法中将 Rpc上下文中的附件(Attachments) 作为调用附件传递给提供者端。具体如下:


当消费者进行服务调用时会依照下面的时序图:
在这里插入图片描述
其中第五步的过程如下:

MockClusterInvoker#invoke => AbstractClusterInvoker#invoke =>  FailoverClusterInvoker#doInvoke

AbstractClusterInvoker#invoke的实现如下:

    @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();

        // binding attachments into invocation.
        // 获取上下文参数
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        // 如果上下文参数不为空,则添加到 RpcInvocation 中。RpcInvocation会随着网络调用传递到提供者端
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }
		// 获取服务提供者列表
        List<Invoker<T>> invokers = list(invocation);
        // 初始化负载均衡策略
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        // 进行调用
        return doInvoke(invocation, invokers, loadbalance);
    }

2. ConsumerContextFilter

ConsumerContextFilter 作用域消费者端。作用是在服务调用结束后清除上下文中的附件信息。

@Activate(group = Constants.CONSUMER, order = -10000)
public class ConsumerContextFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    	// 设置上下文的信息
        RpcContext.getContext()
                .setInvoker(invoker)
                .setInvocation(invocation)
                .setLocalAddress(NetUtils.getLocalHost(), 0)
                .setRemoteAddress(invoker.getUrl().getHost(),
                        invoker.getUrl().getPort());
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }
        try {
            // TODO should we clear server context?
            // 清除 ServerContext
            RpcContext.removeServerContext();
            // 进行服务调用
            return invoker.invoke(invocation);
        } finally {
            // TODO removeContext? but we need to save future for RpcContext.getFuture() API. If clear attachments here, attachments will not available when postProcessResult is invoked.
            // 清除当前线程中添加的附加属性
            RpcContext.getContext().clearAttachments();
        }
    }

    @Override
    public Result onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
    	// 调用返回时设置 ServerContext
        RpcContext.getServerContext().setAttachments(result.getAttachments());
        return result;
    }
}

3. ContextFilter

ContextFilter 作用于提供者端,作用是将Rpc 调用参数中的附件保存到当前 Rpc 上下文中。

@Activate(group = Constants.PROVIDER, order = -10000)
public class ContextFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    	// 获取 Invocation 中的属性添加到上下文中
        Map<String, String> attachments = invocation.getAttachments();
        if (attachments != null) {
        	// 移除一些系统参数
            attachments = new HashMap<String, String>(attachments);
            attachments.remove(Constants.PATH_KEY);
            attachments.remove(Constants.GROUP_KEY);
            attachments.remove(Constants.VERSION_KEY);
            attachments.remove(Constants.DUBBO_VERSION_KEY);
            attachments.remove(Constants.TOKEN_KEY);
            attachments.remove(Constants.TIMEOUT_KEY);
            // Remove async property to avoid being passed to the following invoke chain.
            attachments.remove(Constants.ASYNC_KEY);
            attachments.remove(Constants.TAG_KEY);
            attachments.remove(Constants.FORCE_USE_TAG);
        }
        RpcContext.getContext()
                .setInvoker(invoker)
                .setInvocation(invocation)
//                .setAttachments(attachments)  // merged from dubbox
                .setLocalAddress(invoker.getUrl().getHost(),
                        invoker.getUrl().getPort());

        // merged from dubbox
        // we may already added some attachments into RpcContext before this filter (e.g. in rest protocol)
        // 如果附加属性不为空则设置到上下文对象中。
        if (attachments != null) {
            if (RpcContext.getContext().getAttachments() != null) {
                RpcContext.getContext().getAttachments().putAll(attachments);
            } else {
                RpcContext.getContext().setAttachments(attachments);
            }
        }

        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }
        try {
            return invoker.invoke(invocation);
        } finally {
            // IMPORTANT! For async scenario, we must remove context from current thread, so we always create a new RpcContext for the next invoke for the same thread.
            // 调用结束移除 上下文信息
            RpcContext.removeContext();
            RpcContext.removeServerContext();
        }
    }

    @Override
    public Result onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
        // pass attachments to result
        // 处理结束后,向 ServerContext 中添加附件属性,Result 会随着网络请求返回给 消费者
       
        result.addAttachments(RpcContext.getServerContext().getAttachments());
        return result;
    }
}

四、总结

Dubbo的隐式参数实现比较简单:

  1. 服务调用时在上下文中设置隐式参数
  2. AbstractClusterInvoker#invoke 将上下文中的隐式参数保存到调用参数中
  3. ContextFilter 将调用参数中的隐式参数取出,设置到当前Rpc上下文中,供服务方法使用。

1. 个人的疑问

我们在上面消费者向提供者传递隐式参数的传递是通过 RpcContext.getContext() 来实现,在上面的代码中还看到一个 RpcContext.getServerContext(),用于提供者向消费者传递隐式参数,其相关逻辑如下:

  1. 当消费者发起调用后,提供者端在ContextFilter#invoke 方法中,服务提供者通过 invoker.invoke(invocation);进行服务调用。

  2. ContextFilter#onResponse 方法中结果完成后将 RpcContext.getServerContext() 中的附件保存到 Result 中。
    在这里插入图片描述

  3. 结果返回到消费者端后,ConsumerContextFilter#onResponse 方法会 将接收到的 Result 的附件保存到当前 RpcContext.getServerContext() 中供消费者使用。
    在这里插入图片描述


上述逻辑貌似可以通过 ServerContext 给消费者端传递参数,但是在第一步中,消费者发起调用,当调用结束后会后会清除当前 RpcContext.getServerContext()RpcContext.getContext();,之后再去调用ContextFilter#onResponse。这也就是说,我们在调用方法中无法通过 RpcContext.getServerContext().setAttachment 的来设置附件返回给消费者端。因为调用方法中所使用的上下文在调用结束后就会被清除。
在这里插入图片描述


以上:内容部分参考
《深度剖析Apache Dubbo 核心技术内幕》
https://dubbo.apache.org/zh/docs/v2.7/dev/source/
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

最后

以上就是真实衬衫为你收集整理的Dubbo笔记 ⑲ :隐式参数传递一、前言二、简易demo三、源码分析四、总结的全部内容,希望文章能够帮你解决Dubbo笔记 ⑲ :隐式参数传递一、前言二、简易demo三、源码分析四、总结所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部