乐趣区

关于dubbo:Dubbo-Stub与Mock

前言

大家好,明天开始给大家分享 — Dubbo 专题之 Dubbo StubMock。在前一个章节中咱们介绍了 Dubbo 事件告诉,以及咱们也例举了常见的应用场景并且进行了源码解析来剖析其实现原理,同时晓得 Dubbo 中的事件告诉能够在某个服务调用之前、调用之后、异样产生时触发回调事件,咱们能够通过回调事件做一些额定的工作。咱们在 Dubbo 服务开发过程中可能遇到咱们调用的服务方并没有编写实现,那咱们是不是须要期待服务提供方开发实现咱们能力开始测试呢?那么在本章节咱们会通过介绍 Dubbo StubMock 来解决这个问题。那么什么是 StubMock?上面就让咱们疾速开始吧!

1. 本地 Stub/Mock 简介

在 Dubbo 中提供的 Stub 也可称为本地存根具备相似代理模式的性能,即把咱们调用的真正对象从新包装而后把包装对象提供给调用方,那么在调用真正的对象之前和之后咱们能够做相应解决逻辑。同理 Mock 也可称为本地假装和本地存根具备相似原理只是 Mock 针对产生 RpcException 异样或者咱们强制应用 Mock 形式才去调用 Mock 的实现。从上面咱们能够看进去 StubMock 与代理对象关系:

从图中咱们能够看出的 StubMock 都是实现同一个服务的接口,它们都是通过代理对象来调用近程裸露的服务,而 Mock 紧紧是在调用失败时会触发。

Tips:MockStub 的一个子集,便于服务提供方在客户端执行容错逻辑,因常常须要在呈现 RpcException (比方网络失败,超时等)时进行容错,而在呈现业务异样 (比方登录用户名明码谬误) 时不须要容错,如果用 Stub,可能就须要捕捉并依赖 RpcException 类,而用 Mock 就能够不依赖 RpcException,因为它的约定就是只有呈现 RpcException 时才执行。

2. 应用形式

2.1 Stub 配置形式

<dubbo:service interface="com.foo.BarService" stub="true" />

或者

<dubbo:service interface="com.foo.BarService" stub="com.foo.BarServiceStub" />

Stub 的实现类:

/**
 * @author <a href="http://youngitman.tech"> 青年 IT 男 </a>
 * @version v1.0.0
 * @className BookFacadeStub
 * @description
 * @JunitTest: {@link}
 * @date 2020-11-15 23:43
 **/
public class BookFacadeStub implements BookFacade {

   // 真正近程服务对象
    private BookFacade bookFacade;

    // 必须提供 BookFacade 签名的构造函数
    public BookFacadeStub(BookFacade bookFacade){this.bookFacade = bookFacade;}

    @Override
    public List<Book> queryAll() {

        try {
            
            // 做一些前置解决
            
            return bookFacade.queryAll();} catch (Exception e) {
            // 产生异样做一些解决
            return Lists.newArrayList();}finally {// 做一些后置解决}
        
    }

}

2.2 Mock 配置形式

<dubbo:reference interface="com.foo.BarService" mock="true" />

或者

<dubbo:reference interface="com.foo.BarService" mock="com.foo.BarServiceMock" />

Mock 的实现类:

public class BookFacadeMock implements BookFacade {

    /**
     *
     * 这里咱们能够把服务端的办法执行工夫加大 使之超时就能够触发 Mock 的调用
     *
     * @author liyong 
     * @date 12:18 AM 2020/11/16 
     * @param  
     * @exception 
     * @return List<Book> 
     **/
    @Override
    public List<Book> queryAll() {

        // 你能够伪造容错数据,此办法只在呈现 RpcException 时被执行
        Book book = new Book();
        book.setDesc("default");
        book.setName("default");
        return Lists.newArrayList(book);

    }

}

3. 应用场景

在后面的 Stub/Mock 简介中咱们能够晓得他们都是基于对调用代理对象的包装,也就是调用代理对象后面咱们能够做一些自定的操作。上面咱们简略的介绍一些工作中的应用场景:

  1. 前置校验器:在咱们工作中常常在调用操作前都会做一些校验,如果满足校验条件能力执行前面的逻辑。咱们能够利用 Stub 的形式在调用代理对象前做一些校验工作。
  2. 日志打印:咱们能够利用 Stub 在调用对象后面做一些通用的日志打印进去。
  3. 性能监控:利用 Stub 调用代理对象前后操作的工夫来计算服务调用的工夫。
  4. 服务降级:咱们能够利用 Mock 针对呈现 RpcException 异样时触发 Mock 调用机制来做一些服务降级解决工作,例如:当咱们调用近程服务时近程服务不可用,这时候咱们心愿返回一个兜底的数据就能够应用 Mock 的形式。

5. 示例演示

上面以获取图书列表服务为例进行示例演示。我的项目构造如下:

以下是消费者的配置文件dubbo-consumer-xml.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/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-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-consumer" logger="log4j"/>

    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!-- 开启 Mock/Stub 模式 -->
    <dubbo:reference id="bookFacade"
                     interface="com.muke.dubbocourse.mockstub.api.BookFacade"  mock="true"></dubbo:reference>

</beans>

下面的 XML 配置中咱们应用 mock="true" 示意开启 Mock 模式。上面是服务提供者的 XML 配置dubbo-provider-xml.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/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-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-provider" metadata-type="remote"/>

    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <bean id="bookFacade" class="com.muke.dubbocourse.mockstub.provider.BookFacadeImpl"/>

    <!-- 裸露本地服务为 Dubbo 服务,当 cluster="failover" 指定集群容错模式 -->
    <dubbo:service interface="com.muke.dubbocourse.mockstub.api.BookFacade" ref="bookFacade" stub="true"/>

</beans>

下面的服务提供者配置 stub="true" 示意开启本地存根模式。上面咱们别离配置 StubMock 的实现类:

public class BookFacadeMock implements BookFacade {

    /**
     *
     * 这里咱们能够把服务端的办法执行工夫加大 使之超时就能够触发 Mock 的调用
     *
     * @author liyong
     * @date 12:18 AM 2020/11/16
     * @param
     * @exception
     * @return List<Book>
     **/
    @Override
    public List<Book> queryAll() {

        // 你能够伪造容错数据,此办法只在呈现 RpcException 时被执行
        Book book = new Book();
        book.setDesc("default");
        book.setName("default");
        return Lists.newArrayList(book);

    }

}

这里的 BookFacadeMock 同样实现 BookFacade 接口,当调用 BookFacade 近程服务产生 RpcException 异样时就会调用 BookFacadeMock

public class BookFacadeStub implements BookFacade {

    private BookFacade bookFacade;

    public BookFacadeStub(BookFacade bookFacade){this.bookFacade = bookFacade;}

    @Override
    public List<Book> queryAll() {

        try {

            // 做一些前置解决
            System.out.println("调用办法 queryAll 前置解决");
            return bookFacade.queryAll();} catch (Exception e) {
            // 产生异样做一些解决
            return Lists.newArrayList();}finally {

            // 做一些后置解决
            System.out.println("调用办法 queryAll 后置解决");
        }

    }

}

下面的 BookFacadeStub 实现了接口 BookFacade,同时提供参数类型为 BookFacade 的构造函数。当咱们对接口配置了 stub="true" 咱们获取的对象就是 BookFacadeStub 的实例。

5. 实现原理

上面咱们次要剖析 Stub 的实现其 Mock 的实现也是相似。后面的简介中咱们介绍了对于 StubMock都是生产端对代理对象的包装,所有咱们能够大胆猜想对 StubMock的包装过程就在 ReferenceConfig 类中。首先咱们看到 org.apache.dubbo.config.ReferenceConfig#init 办法外围代码如下:

/**
     *
     * 近程代理对象援用
     *
     * @author liyong
     * @date 5:56 PM 2020/8/26
     * @param
     * @exception
     * @return void
     **/
    public synchronized void init() {
      
        //...
      
        // 本地存根 check
        checkStubAndLocal(interfaceClass);
        //mock 检测
        ConfigValidationUtils.checkMock(interfaceClass, this);

        //...
      
        // 创立近程代理对象
        ref = createProxy(map);

        //...
    }

咱们这里次要探讨 Stub 看 `org.apache.dubbo.config.
AbstractInterfaceConfig#checkStubAndLocal` 核心内容如下:

/**
     * 本地存根检测
     */
    public void checkStubAndLocal(Class<?> interfaceClass) {if (ConfigUtils.isNotEmpty(local)) {
           // 加载以接口名称 +Local 的 Class
            Class<?> localClass = ConfigUtils.isDefault(local) ?
                    ReflectUtils.forName(interfaceClass.getName() + "Local") : ReflectUtils.forName(local);
          // 校验该 Class 是否可能加载  
          verify(interfaceClass, localClass);
        }
        if (ConfigUtils.isNotEmpty(stub)) {
             // 加载以接口名称 +Local 的 Class
            Class<?> localClass = ConfigUtils.isDefault(stub) ?
                    ReflectUtils.forName(interfaceClass.getName() + "Stub") : ReflectUtils.forName(stub);
          // 校验该 Class 是否可能加载  
          verify(interfaceClass, localClass);
        }
    }

下面的代码加载 XxxLocalXxxStubClass,校验办法 `org.apache.dubbo.config.
AbstractInterfaceConfig#verify` 核心内容如下:

    private void verify(Class<?> interfaceClass, Class<?> localClass) {
        // 判断该 localClass 是否为 interfaceClass 的子类型
        if (!interfaceClass.isAssignableFrom(localClass)) {throw new IllegalStateException("The local implementation class" + localClass.getName() +
                    "not implement interface" + interfaceClass.getName());
        }

        try {
            // 判断 localClass 构造函数是否以 interfaceClass 类型的参数签名
            ReflectUtils.findConstructor(localClass, interfaceClass);
        } catch (NoSuchMethodException e) {throw new IllegalStateException("No such constructor \"public "+ localClass.getSimpleName() +"("+ interfaceClass.getName() +")\"in local implementation class" + localClass.getName());
        }
    }

下面的办法次要是校验 localClass (例如:XxxLocalXxxStub)是否实现 interfaceClass 接口并且以 interfaceClass 参数签名的构造函数。接下来创立代理对象调用办法 `org.apache.dubbo.config.ReferenceConfig

createProxy最终会调用到org.apache.dubbo.rpc.proxy.wrapper

.StubProxyFactoryWrapper

getProxy(org.apache.dubbo.rpc.Invoker<T>)` 办法:

public <T> T getProxy(Invoker<T> invoker) throws RpcException {T proxy = proxyFactory.getProxy(invoker);
        if (GenericService.class != invoker.getInterface()) {URL url = invoker.getUrl();
            // 获取本地存根配置
            String stub = url.getParameter(STUB_KEY, url.getParameter(LOCAL_KEY));
            if (ConfigUtils.isNotEmpty(stub)) {Class<?> serviceType = invoker.getInterface();
                if (ConfigUtils.isDefault(stub)) {
                    // 尝试获取 stub 如果未查找到默认应用 XxxLocal
                    if (url.hasParameter(STUB_KEY)) {stub = serviceType.getName() + "Stub";
                    } else {stub = serviceType.getName() + "Local";
                    }
                }
                try {
                    // 加载 XxxStub 或 XxxLocal
                    Class<?> stubClass = ReflectUtils.forName(stub);
                    if (!serviceType.isAssignableFrom(stubClass)) {throw new IllegalStateException("The stub implementation class" + stubClass.getName() + "not implement interface" + serviceType.getName());
                    }
                    try {
                        // 获取参数签名为接口类型的构造函数
                        Constructor<?> constructor = ReflectUtils.findConstructor(stubClass, serviceType);
                        // 包装近程代理对象,在调用前先执行本地逻辑(*Stub、*Local)
                        proxy = (T) constructor.newInstance(new Object[]{proxy});
                        // 裸露包装后的对象
                        URLBuilder urlBuilder = URLBuilder.from(url);
                        if (url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT)) {
                           //..
                            try {export(proxy, (Class) invoker.getInterface(), urlBuilder.build());
                            } catch (Exception e) {LOGGER.error("export a stub service error.", e);
                            }
                        }
                    } catch (NoSuchMethodException e) {//..}
                } catch (Throwable t) {//..}
            }
        }
        return proxy;
    }

从下面的代码中能够看出近程调用代理对象传递给 XxxStubXxxLocal模式的实例构造函数,并且把 XxxStubXxxLocal返回给 ReferenceConfig.ref,这样咱们应用 Dubbo 服务是获取的就是XxxStubXxxLocal的实例对象。

6. 小结

在本大节中咱们次要学习了 Dubbo 中 StubMock以及两种模式的应用形式。同时也剖析了 StubMock 实现的原理,其本质上是通过包装对援用代理对象的调用,其中 MockStub 实现的一个子集针对调用抛出 RpcException 异样时被调用。

本节课程的重点如下:

  1. 了解 Dubbo StubMock
  2. 理解了两种模式应用形式
  3. 理解 StubMock 实现原理
  4. 理解 StubMock 应用场景

作者

集体从事金融行业,就任过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就任于某银行负责对立领取零碎建设。本身对金融行业有强烈的喜好。同时也实际大数据、数据存储、自动化集成和部署、散布式微服务、响应式编程、人工智能等畛域。同时也热衷于技术分享创建公众号和博客站点对常识体系进行分享。关注公众号:青年 IT 男 获取最新技术文章推送!

博客地址: http://youngitman.tech

微信公众号:

退出移动版