概述
文章目录
- 一、前言
- 二、简易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的隐式参数实现比较简单:
- 服务调用时在上下文中设置隐式参数
- AbstractClusterInvoker#invoke 将上下文中的隐式参数保存到调用参数中
- ContextFilter 将调用参数中的隐式参数取出,设置到当前Rpc上下文中,供服务方法使用。
1. 个人的疑问
我们在上面消费者向提供者传递隐式参数的传递是通过 RpcContext.getContext() 来实现,在上面的代码中还看到一个 RpcContext.getServerContext(),用于提供者向消费者传递隐式参数,其相关逻辑如下:
-
当消费者发起调用后,提供者端在
ContextFilter#invoke
方法中,服务提供者通过invoker.invoke(invocation);
进行服务调用。 -
在
ContextFilter#onResponse
方法中结果完成后将 RpcContext.getServerContext() 中的附件保存到 Result 中。
-
结果返回到消费者端后,
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三、源码分析四、总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复