motan源码分析四:客户端调用服务

在第一章中,我们分析了服务的发布与注册,本章中将简单的分析一下客户端调用服务的代码及流程,本文将以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对象进行信息的传递。

时间: 2024-08-28 12:48:46

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

motan源码分析五:cluster相关

上一章我们分析了客户端调用服务端相关的源码,但是到了cluster里面的部分我们就没有分析了,本章将深入分析cluster和它的相关支持类. 1.clustersupport的创建过程,上一章的ReferConfig的initRef()方法中调用了相关的创建代码: for(Iterator iterator = protocols.iterator(); iterator.hasNext();) { ProtocolConfig protocol = (ProtocolConfig)iterat

baksmali和smali源码分析(四)

baksmali 首先执行的第一个main 函数     public static void main(String[] args) throws IOException {         Locale locale = new Locale("en", "US");         Locale.setDefault(locale);         CommandLineParser parser = new PosixParser();         C

zookeeper源码分析之一客户端发送请求流程

znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的集中管理,集群管理,分布式锁等等. 知识准备: zookeeper定义的状态有: Unknown (-1),Disconnected (0),NoSyncConnected (1),SyncConnected (3),AuthFailed (4),ConnectedReadOnly (5),Sasl

Nouveau源码分析(四):NVIDIA设备初始化之nouveau_drm_load (1)

Nouveau源码分析(四) probe函数成功返回之后,DRM模块就会调用struct drm_driver的load函数,对应nouveau的nouveau_drm_load. 这个函数虽然看起来不是特别长,但每一个调用的函数展开后就会变得非常长了! // /drivers/gpu/drm/nouveau/nouveau_drm.c 364 static int 365 nouveau_drm_load(struct drm_device *dev, unsigned long flags)

mybatis源码分析(四) mybatis与spring事务管理分析

mybatis源码分析(四) mybatis与spring事务管理分析 一丶从jdbc的角度理解什么是事务 从mysql获取一个连接之后, 默认是自动提交, 即执行完sql之后, 就会提交事务. 这种事务的范围是一条sql语句. 将该连接设置非自动提交, 可以执行多条sql语句, 然后由程序决定是提交事务, 还是回滚事务. 这也是我们常说的事务. Connection connection = dataSource.getConnection(); // connection.setTransa

Tomcat7.0源码分析——启动与停止服务

前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程. 由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.s

ABP源码分析四十七:ABP中的异常处理

ABP 中异常处理的思路是很清晰的.一共五种类型的异常类. AbpInitializationException用于封装ABP初始化过程中出现的异常,只要抛出AbpInitializationException异常就可以,无须做额外处理.这类异常往往是需要维护人员介入分析的. 其他四个异常都在AbpController中被集中处理,处理分为两步:一,通过EventBus触发异常事件,相应的异常处理函数则处理异常.而针对AbpValidationException,UserFriendlyExce

Tomcat7.0源码分析——启动与停止服务原理

前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程. 由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.s

Tomcat源码分析——启动与停止服务

前言 熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的.对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程. 由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.s