在第一章中,我们分析了服务的发布与注册,本章中将简单的分析一下客户端调用服务的代码及流程,本文将以spring加载的方式进行分析。

1.在DemoRpcClient类的main()方法中加载类:

        ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"classpath:motan_demo_client.xml"});

        MotanDemoService service = (MotanDemoService) ctx.getBean("motanDemoReferer");

2.上面加载了spring的配置文件motan_demo_client.xml

    <motan:registry regProtocol="zookeeper" name="registry" address="127.0.0.1:2181" connectTimeout="2000"/>

    <!-- motan协议配置 -->
<motan:protocol default="true" name="motan" haStrategy="failover"
loadbalance="roundrobin" maxClientConnection="10" minClientConnection="2"/> <!-- 通用referer基础配置 -->
<motan:basicReferer requestTimeout="200" accessLog="false"
retries="2" group="motan-demo-rpc" module="motan-demo-rpc"
application="myMotanDemo" protocol="motan" registry="registry"
id="motantestClientBasicConfig" throwException="false" check="true"/> <!-- 具体referer配置。使用方通过beanid使用服务接口类 -->
<motan:referer id="motanDemoReferer"
interface="com.weibo.motan.demo.service.MotanDemoService"
connectTimeout="300" requestTimeout="300" basicReferer="motantestClientBasicConfig"/>

经过spring装载RefererConfig后,每次向spring框架getBean时会调用RefererConfig的getRef()方法

3.获取接口MotanDemoService的实现类代码如下:

    public Object getRef()
{
if(ref == null)
initRef();//初始化
return ref;
} public synchronized void initRef()
{
if(initialized.get())
return;
try
{
interfaceClass = Class.forName(interfaceClass.getName(), true, Thread.currentThread().getContextClassLoader());
}
catch(ClassNotFoundException e)
{
throw new MotanFrameworkException((new StringBuilder("ReferereConfig initRef Error: Class not found ")).append(interfaceClass.getName()).toString(), e, MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
}
if(CollectionUtil.isEmpty(protocols))//protocol配置是否为空
throw new MotanFrameworkException(String.format("%s RefererConfig is malformed, for protocol not set correctly!", new Object[] {
interfaceClass.getName()
}));
checkInterfaceAndMethods(interfaceClass, methods);
clusterSupports = new ArrayList(protocols.size());//初始化集群支持类列表,可以支持多个
List clusters = new ArrayList(protocols.size());//初始化集群类列表,可以支持多个
String proxy = null;
ConfigHandler configHandler = (ConfigHandler)ExtensionLoader.getExtensionLoader(com/weibo/api/motan/config/handler/ConfigHandler).getExtension("default");//加载SimpleConfigHandler
List registryUrls = loadRegistryUrls();//加载注册中心url列表,可以支持多个注册中心
String localIp = getLocalHostAddress(registryUrls);
for(Iterator iterator = protocols.iterator(); iterator.hasNext();)
{
ProtocolConfig protocol = (ProtocolConfig)iterator.next();
LoggerUtil.info((new StringBuilder("ProtocolConfig's")).append(protocol.getName()).toString());
Map params = new HashMap();
params.put(URLParamType.nodeType.getName(), "referer");
params.put(URLParamType.version.getName(), URLParamType.version.getValue());
params.put(URLParamType.refreshTimestamp.getName(), String.valueOf(System.currentTimeMillis()));
collectConfigParams(params, new AbstractConfig[] {
protocol, basicReferer, extConfig, this
});
collectMethodConfigParams(params, getMethods());
URL refUrl = new URL(protocol.getName(), localIp, 0, interfaceClass.getName(), params);
ClusterSupport clusterSupport = createClusterSupport(refUrl, configHandler, registryUrls);
clusterSupports.add(clusterSupport);
clusters.add(clusterSupport.getCluster());
proxy = proxy != null ? proxy : refUrl.getParameter(URLParamType.proxy.getName(), URLParamType.proxy.getValue());
} ref = configHandler.refer(interfaceClass, clusters, proxy);//调用SimpleConfigHandler的refer方法获取接口实现类
initialized.set(true);
}

4.下面我们来看一下SimpleConfigHandler的refer方法的代理实现,使用了jdk的动态代理技术

    public <T> T refer(Class<T> interfaceClass, List<Cluster<T>> clusters, String proxyType) {
ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(proxyType);//创建代理工厂
return proxyFactory.getProxy(interfaceClass, new RefererInvocationHandler<T>(interfaceClass, clusters));//获取代理类
} public class JdkProxyFactory implements ProxyFactory { @SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> clz, InvocationHandler invocationHandler) {
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {clz}, invocationHandler);//使用jdk的动态代理,实际调用的代码是下面的的invoke方法
} } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
DefaultRequest request = new DefaultRequest();//封装通信用的request,request和response是在客户端和服务端通信的两个对象 request.setRequestId(RequestIdGenerator.getRequestId());
request.setArguments(args);
request.setMethodName(method.getName());
request.setParamtersDesc(ReflectUtil.getMethodParamDesc(method));
request.setInterfaceName(clz.getName());
request.setAttachment(URLParamType.requestIdFromClient.getName(), String.valueOf(RequestIdGenerator.getRequestIdFromClient())); // 当 referer配置多个protocol的时候,比如A,B,C,
// 那么正常情况下只会使用A,如果A被开关降级,那么就会使用B,B也被降级,那么会使用C
for (Cluster<T> cluster : clusters) {//motan支持多个protocol的配置,也就是支持多个cluster,但是默认情况下只取第一个,如果前面的被降级,则取下一个
String protocolSwitcher = MotanConstants.PROTOCOL_SWITCHER_PREFIX + cluster.getUrl().getProtocol(); Switcher switcher = switcherService.getSwitcher(protocolSwitcher); if (switcher != null && !switcher.isOn()) {
continue;
}
List<Referer<T>> referL = cluster.getReferers();//此段代码为我单独添加的,目的是证明客户端在配置多个注册中心的情况下,cluster可以支持跨注册中心的调用
for(Referer<T> refer : referL){
LoggerUtil.info(refer.getServiceUrl().getUri()+refer.getServiceUrl().getPath());
}
request.setAttachment(URLParamType.version.getName(), cluster.getUrl().getVersion());
request.setAttachment(URLParamType.clientGroup.getName(), cluster.getUrl().getGroup());
// 带上client的application和module
request.setAttachment(URLParamType.application.getName(), ApplicationInfo.getApplication(cluster.getUrl()).getApplication());
request.setAttachment(URLParamType.module.getName(), ApplicationInfo.getApplication(cluster.getUrl()).getModule());
Response response = null;
boolean throwException =
Boolean.parseBoolean(cluster.getUrl().getParameter(URLParamType.throwException.getName(),
URLParamType.throwException.getValue()));
try {
response = cluster.call(request);//调用cluster的call方法
return response.getValue();//获取返回信息
} catch (RuntimeException e) {
if (ExceptionUtil.isBizException(e)) {
Throwable t = e.getCause();
// 只抛出Exception,防止抛出远程的Error
if (t != null && t instanceof Exception) {
throw t;
} else {
String msg =
t == null ? "biz exception cause is null" : ("biz exception cause is throwable error:" + t.getClass()
+ ", errmsg:" + t.getMessage());
throw new MotanServiceException(msg, MotanErrorMsgConstant.SERVICE_DEFAULT_ERROR);
}
} else if (!throwException) {
LoggerUtil.warn("RefererInvocationHandler invoke false, so return default value: uri=" + cluster.getUrl().getUri()
+ " " + MotanFrameworkUtil.toString(request), e);
return getDefaultReturnValue(method.getReturnType());
} else {
LoggerUtil.error(
"RefererInvocationHandler invoke Error: uri=" + cluster.getUrl().getUri() + " "
+ MotanFrameworkUtil.toString(request), e);
throw e;
}
}
} throw new MotanServiceException("Referer call Error: cluster not exist, interface=" + clz.getName() + " "
+ MotanFrameworkUtil.toString(request), MotanErrorMsgConstant.SERVICE_UNFOUND); }

本章知识点总结:

1.客户端在获取业务接口的实现类时,使用了jdk的动态代理技术;

2.客户端可以支持多个注册中心;

3.客户端可以支持多个cluster,但是只取最前面有效那个;

4.使用request和response对象进行信息的传递。

motan源码分析四:客户端调用服务的更多相关文章

  1. 【Netty源码分析】客户端connect服务端过程

    上一篇博客[Netty源码分析]Netty服务端bind端口过程 我们介绍了服务端绑定端口的过程,这一篇博客我们介绍一下客户端连接服务端的过程. ChannelFuture future = boos ...

  2. motan源码分析八:涉及到底层的客户端调用

    之前我们分析了客户端调用服务端的源码,但是没有涉及到通讯层和序列化层,本文将之前讲过的内容做一次串联. 1.上层通过动态代理调用refer的call,每个refer又对应一个nettyclient,下 ...

  3. dubbo源码分析2-reference bean发起服务方法调用

    dubbo源码分析1-reference bean创建 dubbo源码分析2-reference bean发起服务方法调用 dubbo源码分析3-service bean的创建与发布 dubbo源码分 ...

  4. motan源码分析五:cluster相关

    上一章我们分析了客户端调用服务端相关的源码,但是到了cluster里面的部分我们就没有分析了,本章将深入分析cluster和它的相关支持类. 1.clustersupport的创建过程,上一章的Ref ...

  5. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. Zookeeper 源码(四)Zookeeper 服务端源码

    Zookeeper 源码(四)Zookeeper 服务端源码 Zookeeper 服务端的启动入口为 QuorumPeerMain public static void main(String[] a ...

  8. Spring源码分析之`BeanFactoryPostProcessor`调用过程

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 本文内容: AbstractApplicationContext#refresh前部分的一点小内容 ...

  9. motan源码分析六:客户端与服务器的通信层分析

    本章将分析motan的序列化和底层通信相关部分的代码. 1.在上一章中,有一个getrefers的操作,来获取所有服务器的引用,每个服务器的引用都是由DefaultRpcReferer来创建的 pub ...

随机推荐

  1. 开发solr功能问题点

    Criteria criteria = new Criteria(); public Criteria and(Criteria criteria) { this.criteriaChain.add( ...

  2. (转)如何将ecshop首页主广告位的flash轮播替换为js轮播

    转之--http://www.ecshoptemplate.com/article-1710.html 这个ecshop很常见,因为现在比起flash难以修改,js更加符合人们的使用习惯,而默认ecs ...

  3. (转)PHP中的ob_start用法详解

    用PHP的ob_start();控制您的浏览器cache Output Control 函数可以让你自由控制脚本中数据的输出.它非常地有用,特别是对于:当你想在数据已经输出后,再输出文件头的情况.输出 ...

  4. MVC3 展示数据含有html代码处理,配合上篇发布的StringHelper

    @Html.Raw(@StringHelper.SubstringToHTML(Content,30)) StringHelper 地址:http://www.cnblogs.com/Jiawt/p/ ...

  5. Android开发手记(24) Log的使用及颜色的更改

    在程序开发过程中,LOG是广泛使用的用来记录程序执行过程的机制,它既可以用于程序调试,也可以用于产品运营中的事件记录.在Android系统中,提供了简单.便利的LOG机制,开发人员可以方便地使用.本文 ...

  6. Swift - 19 - 字典的初始化

    //: Playground - noun: a place where people can play import UIKit // 注意: swift中的字典用的也是中括号, 和OC的大括号是不 ...

  7. javascript基础学习(十三)

    javascript之文档对象 学习要点: 文档对象 文档对象的应用 一.文档对象 Document对象是代表一个浏览器窗口或框架中的显示HTML文件的对象.javascript会为每个HTML文档自 ...

  8. javascript基础学习(十一)

    javascript之BOM 学习要点: BOM介绍 Window对象 一.BOM介绍 浏览器对象模型简称为BOM(Brower Object Model),BOM由很多对象构成,对象与对象之间有着相 ...

  9. java对mysql数据库进行单表筛选备份、还原操作

    最近在做的一个项目需要对mysql数据库中的单个表格进行备份 其中,一部分表格需要进行筛选备份(例如对最近插入的1000条记录进行备份) 思路:java调用系统命令完成备份操作 假设现在有数据库tes ...

  10. FFMPEG 截取RTMP直播流图片命令

    CentOS 6.5  yum安装FFMPEG步骤 1. 手动添加yum源配置 vi /etc/yum.repos.d/dag.repo   [dag] name=Dag RPM Repository ...