美文网首页dubbodubbo
dubbo的Mock功能与源码实现

dubbo的Mock功能与源码实现

作者: 一滴水的坚持 | 来源:发表于2018-03-10 18:22 被阅读0次

    在开发自测,联调过程中,经常碰到一些下游服务调用不通的场景,这个时候我们如何不依赖于下游系统,就业务系统独立完成自测?
    dubbo自身是支持mock服务的,在reference标签里,有一个参数mock,该参数有四个值,false,default,true,或者Mock类的类名。分别代表如下含义:

    • false,不调用mock服务。
    • true,当服务调用失败时,使用mock服务。
    • default,当服务调用失败时,使用mock服务。
    • force,强制使用Mock服务(不管服务能否调用成功)。(使用xml配置不生效,使用ReferenceConfigAPI可以生效)

    使用方法:

    • 将mock参数启用,在<dubbo:reference>中添加参数项mock=true。

    • 实现需要调用的服务接口

      • 上游系统需要调用下游系统,则下游需要提供jar包给上有系统,该jar包只有接口,没有实现。我们需要实现该接口,且命名必须是该接口名+Mock,例如原接口是com.alibaba.dubbo.demo.DemoService,则实现类必须是com.alibaba.dubbo.demo.DemoServiceMock
        举个例子:
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
           xmlns="http://www.springframework.org/schema/beans"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    
        <!-- consumer's application name, used for tracing dependency relationship (not a matching criterion),
        don't set it same as provider -->
        <dubbo:application name="demo-consumer"/>
    
        <!-- use multicast registry center to discover service -->
        <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
    
        <!-- generate proxy for the remote service, then demoService can be used in the same way as the
        local regular interface -->
        <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService"/>
    
    </beans>
    
    public class Consumer {
        public static void main(String[] args) {
            //Prevent to get IPV6 address,this way only work in debug mode
            //But you can pass use -Djava.net.preferIPv4Stack=true,then it work well whether in debug mode or not
            System.setProperty("java.net.preferIPv4Stack", "true");
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
            context.start();
            DemoService demoService = (DemoService) context.getBean("demoService"); // get remote service proxy
            try {
                Thread.sleep(1000);
                String hello = demoService.sayHello("world"); // call remote method
                System.out.println(hello); // get result
    
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
    }
    

    当我下游服务不启动的时候,也就是没有com.alibaba.dubbo.demo.DemoService的时候,运行该main函数,执行结果如下:

    com.alibaba.dubbo.rpc.RpcException: No provider available from registry 127.0.0.1:2181 for service com.alibaba.dubbo.demo.DemoService on consumer 192.168.34.220 use dubbo version 2.0.0, please check status of providers(disabled, not registered or in blacklist).
        at com.alibaba.dubbo.registry.integration.RegistryDirectory.doList(RegistryDirectory.java:574)
        at com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory.list(AbstractDirectory.java:73)
        at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.list(AbstractClusterInvoker.java:265)
        at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:224)
        at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:70)
        at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:51)
        at com.alibaba.dubbo.common.bytecode.proxy0.sayHello(proxy0.java)
        at com.alibaba.dubbo.demo.consumer.Consumer.main(Consumer.java:35)
    

    当我将mock="true"加载demoService上以后,执行结果如下:

     <dubbo:reference id="demoService" check="false"  mock="true" interface="com.alibaba.dubbo.demo.DemoService"/>
    
    Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'demoService': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Not found class com.alibaba.dubbo.demo.DemoServiceMock, cause: com.alibaba.dubbo.demo.DemoServiceMock
        at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:175)
        at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:103)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1634)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:254)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1078)
        at com.alibaba.dubbo.demo.consumer.Consumer.main(Consumer.java:30)
    Caused by: java.lang.IllegalStateException: Not found class com.alibaba.dubbo.demo.DemoServiceMock, cause: com.alibaba.dubbo.demo.DemoServiceMock
        at com.alibaba.dubbo.common.utils.ReflectUtils.forName(ReflectUtils.java:605)
        at com.alibaba.dubbo.config.AbstractInterfaceConfig.checkStubAndMock(AbstractInterfaceConfig.java:313)
        at com.alibaba.dubbo.config.ReferenceConfig.init(ReferenceConfig.java:279)
        at com.alibaba.dubbo.config.ReferenceConfig.get(ReferenceConfig.java:163)
        at com.alibaba.dubbo.config.spring.ReferenceBean.getObject(ReferenceBean.java:65)
        at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168)
        ... 6 more
    Caused by: java.lang.ClassNotFoundException: com.alibaba.dubbo.demo.DemoServiceMock
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:348)
        at com.alibaba.dubbo.common.utils.ReflectUtils.name2class(ReflectUtils.java:668)
        at com.alibaba.dubbo.common.utils.ReflectUtils.name2class(ReflectUtils.java:618)
        at com.alibaba.dubbo.common.utils.ReflectUtils.forName(ReflectUtils.java:603)
        ... 11 more
    

    新建一个类:

    package com.alibaba.dubbo.demo;
    
    /**
     * Created by jetty on 18/3/10.
     */
    public class DemoServiceMock implements DemoService{
        @Override
        public String sayHello(String name) {
            return "hello world";
        }
    }
    
    
    om.alibaba.dubbo.rpc.RpcException: No provider available from registry 127.0.0.1:2181 for service com.alibaba.dubbo.demo.DemoService on consumer 192.168.34.220 use dubbo version 2.0.0, please check status of providers(disabled, not registered or in blacklist).
        at com.alibaba.dubbo.registry.integration.RegistryDirectory.doList(RegistryDirectory.java:574)
        at com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory.list(AbstractDirectory.java:73)
        at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.list(AbstractClusterInvoker.java:265)
        at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:224)
        at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:80)
        at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:51)
        at com.alibaba.dubbo.common.bytecode.proxy0.sayHello(proxy0.java)
        at com.alibaba.dubbo.demo.consumer.Consumer.main(Consumer.java:33)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
    [10/03/18 05:45:06:006 CST] main  INFO wrapper.MockClusterInvoker:  [DUBBO] Exception when try to invoke mock. Get mock invokers error for service:com.alibaba.dubbo.demo.DemoService, method:sayHello, will contruct a new mock with 'new MockInvoker()'., dubbo version: 2.0.0, current host: 192.168.34.220
    com.alibaba.dubbo.rpc.RpcException: No provider available from registry 127.0.0.1:2181 for service com.alibaba.dubbo.demo.DemoService on consumer 192.168.34.220 use dubbo version 2.0.0, please check status of providers(disabled, not registered or in blacklist).
        at com.alibaba.dubbo.registry.integration.RegistryDirectory.doList(RegistryDirectory.java:574)
        at com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory.list(AbstractDirectory.java:73)
        at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.selectMockInvoker(MockClusterInvoker.java:145)
        at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.doMockInvoke(MockClusterInvoker.java:100)
        at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:88)
        at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:51)
        at com.alibaba.dubbo.common.bytecode.proxy0.sayHello(proxy0.java)
        at com.alibaba.dubbo.demo.consumer.Consumer.main(Consumer.java:33)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
    hello world
    

    可以看到结果,先去调用下游服务,发现没服务,报错,然后调用Mock服务,返回hello world。
    当我将mock参数改为force的时候再执行,抛异常了。

    Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'demoService': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Not found class force, cause: force
        at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:175)
        at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:103)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1634)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:254)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1078)
        at com.alibaba.dubbo.demo.consumer.Consumer.main(Consumer.java:30)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
    Caused by: java.lang.IllegalStateException: Not found class force, cause: force
        at com.alibaba.dubbo.common.utils.ReflectUtils.forName(ReflectUtils.java:605)
        at com.alibaba.dubbo.config.AbstractInterfaceConfig.checkStubAndMock(AbstractInterfaceConfig.java:313)
        at com.alibaba.dubbo.config.ReferenceConfig.init(ReferenceConfig.java:279)
        at com.alibaba.dubbo.config.ReferenceConfig.get(ReferenceConfig.java:163)
        at com.alibaba.dubbo.config.spring.ReferenceBean.getObject(ReferenceBean.java:65)
        at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168)
        ... 11 more
    Caused by: java.lang.ClassNotFoundException: force
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:348)
        at com.alibaba.dubbo.common.utils.ReflectUtils.name2class(ReflectUtils.java:668)
        at com.alibaba.dubbo.common.utils.ReflectUtils.name2class(ReflectUtils.java:618)
        at com.alibaba.dubbo.common.utils.ReflectUtils.forName(ReflectUtils.java:603)
        ... 16 more
    

    报错在

     at com.alibaba.dubbo.common.utils.ReflectUtils.forName(ReflectUtils.java:605)
        at com.alibaba.dubbo.config.AbstractInterfaceConfig.checkStubAndMock(AbstractInterfaceConfig.java:313)
        at com.alibaba.dubbo.config.ReferenceConfig.init(ReferenceConfig.java:279)
        at com.alibaba.dubbo.config.ReferenceConfig.get(ReferenceConfig.java:163)
    

    当dubbo服务在启动获取bean服务的时候,首先会检查mock属性是不是以return开头,不是的话判断是否是default,或者true,如果是default,或者true开头,则返回接口名+Mock为Mock实例的类名,不然,则直接以Mock属性的值为类名
    因此如果这里为force,则会抛: Not found class force, 异常。

     protected void checkStubAndMock(Class<?> interfaceClass) {
        if (ConfigUtils.isNotEmpty(mock)) {
            //是不是以return开头
            if (mock.startsWith(Constants.RETURN_PREFIX)) {
                String value = mock.substring(Constants.RETURN_PREFIX.length());
                try {
                    MockInvoker.parseMockValue(value);
                } catch (Exception e) {
                    throw new IllegalStateException("Illegal mock json value in <dubbo:service ... mock=\"" + mock + "\" />");
                }
            } else {
                //判断是否是true/default,如果是返回接口名+Mock,如果不是,则直接返回Mock的参数
                Class<?> mockClass = ConfigUtils.isDefault(mock) ? ReflectUtils.forName(interfaceClass.getName() + "Mock") : ReflectUtils.forName(mock);
                if (!interfaceClass.isAssignableFrom(mockClass)) {
                    throw new IllegalStateException("The mock implementation class " + mockClass.getName() + " not implement interface " + interfaceClass.getName());
                }
                try {
                    mockClass.getConstructor(new Class<?>[0]);
                } catch (NoSuchMethodException e) {
                    throw new IllegalStateException("No such empty constructor \"public " + mockClass.getSimpleName() + "()\" in mock implementation class " + mockClass.getName());
                }
            }
        }
    }
    

    重点来了,当我不使用xml配置时,直接使用referenceConfig获取下游服务,可以调用成功,返回hello,world。重点在于consumerConfig.setMock("force");这行代码。

     @Test
    public void testInjvm() throws Exception {
        ApplicationConfig application = new ApplicationConfig();
        application.setName("test-protocol-random-port");
    
        RegistryConfig registry = new RegistryConfig();
        registry.setAddress("multicast://224.5.6.7:1234");
        ProtocolConfig protocol = new ProtocolConfig();
        protocol.setName("dubbo");
        ReferenceConfig<DemoService> rc = new ReferenceConfig<DemoService>();
        rc.setApplication(application);
        rc.setRegistry(registry);
        ConsumerConfig consumerConfig=new ConsumerConfig();
        //重点在这里
        consumerConfig.setMock("force");
        rc.check=false;
        rc.setConsumer(consumerConfig);
        rc.setInterface(DemoService.class.getName());
    
        try {
            DemoService demoService2=  rc.get();
            String text=demoService2.sayName("hello");
            System.out.println(text);
            Assert.assertTrue(!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(
                    rc.getInvoker().getUrl().getProtocol()));
        } finally {
        }
    }
    

    那么问题来了,到底这个算不算bug。。。。。
    不关心这个了,重点是原理,mock是如何实现的。
    再看看调用过程:

    • 查看是否有Mock的参数,没有或false,则直接调用invoker。
    • 若是以force开头,则强制走Mock
    • 其他场景,先调用下游,失败了调用Mock服务
      在调用Mock服务过程中,当发现没有Mock服务时,new一个MockInvoker,调用MockInvoker的服务。
    public class MockClusterInvoker<T> implements Invoker<T> {
    
     
       public Result invoke(Invocation invocation) throws RpcException {
           Result result = null;
           //判断是否有Mock参数
           String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
           if (value.length() == 0 || value.equalsIgnoreCase("false")) {
              
               result = this.invoker.invoke(invocation);
           } else if (value.startsWith("force")) {
               Mock参数是否以force开头
               if (logger.isWarnEnabled()) {
                   logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
               }
               //force:direct mock 强制走Mock
               result = doMockInvoke(invocation, null);
           } else {
               //fail-mock
               try {
                   result = this.invoker.invoke(invocation);
               } catch (RpcException e) {
                   if (e.isBiz()) {
                       throw e;
                   } else {
                       if (logger.isWarnEnabled()) {
                           logger.info("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
                       }
                       //失败走Mock
                       result = doMockInvoke(invocation, e);
                   }
               }
           }
           return result;
       }
    
       private Result doMockInvoke(Invocation invocation, RpcException e) {
           Result result = null;
           Invoker<T> minvoker;
           //获取Mock的Invoker
           List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
           if (mockInvokers == null || mockInvokers.size() == 0) {
               ////没有获取到Mock的Invoker 新建一个MockInvoker
               minvoker = (Invoker<T>) new MockInvoker(directory.getUrl());
           } else {
               minvoker = mockInvokers.get(0);
           }
           try {
               //调用Mock服务。
               result = minvoker.invoke(invocation);
           } catch (RpcException me) {
               if (me.isBiz()) {
                   result = new RpcResult(me.getCause());
               } else {
                   throw new RpcException(me.getCode(), getMockExceptionMessage(e, me), me.getCause());
               }
           } catch (Throwable me) {
               throw new RpcException(getMockExceptionMessage(e, me), me.getCause());
           }
           return result;
       }
    }
    

    在调用过程中,先从缓存中获取,获取不到,调用newInstance实例化一个。

    //MockInvoker.java
     public Result invoke(Invocation invocation) throws RpcException {
        //忽略很多代码
        Invoker<T> invoker = getInvoker(mock);
        return invoker.invoke(invocation);   
    }
    
     private Invoker<T> getInvoker(String mockService) {
        //缓存中获取
        Invoker<T> invoker = (Invoker<T>) mocks.get(mockService);
        if (invoker != null) {
            return invoker;
        } else {
            //newInstance获取的Mock服务实例
            T mockObject = (T) mockClass.newInstance();
            invoker = proxyFactory.getInvoker(mockObject, (Class<T>) serviceType, url);
            return invoker;
        }
    }
    

    fyi

    相关文章

      网友评论

        本文标题:dubbo的Mock功能与源码实现

        本文链接:https://www.haomeiwen.com/subject/rpkbfftx.html