前言
大家好,明天开始给大家分享 — Dubbo 专题之 Dubbo Stub
和 Mock
。在前一个章节中咱们介绍了 Dubbo 事件告诉,以及咱们也例举了常见的应用场景并且进行了源码解析来剖析其实现原理,同时晓得 Dubbo 中的事件告诉能够在某个服务调用之前、调用之后、异样产生时触发回调事件,咱们能够通过回调事件做一些额定的工作。咱们在 Dubbo 服务开发过程中可能遇到咱们调用的服务方并没有编写实现,那咱们是不是须要期待服务提供方开发实现咱们能力开始测试呢?那么在本章节咱们会通过介绍 Dubbo Stub
和 Mock
来解决这个问题。那么什么是 Stub
和 Mock
?上面就让咱们疾速开始吧!
1. 本地 Stub/Mock 简介
在 Dubbo 中提供的 Stub
也可称为本地存根具备相似代理模式的性能,即把咱们调用的真正对象从新包装而后把包装对象提供给调用方,那么在调用真正的对象之前和之后咱们能够做相应解决逻辑。同理 Mock
也可称为本地假装和本地存根具备相似原理只是 Mock
针对产生 RpcException
异样或者咱们强制应用 Mock
形式才去调用 Mock
的实现。从上面咱们能够看进去 Stub
和 Mock
与代理对象关系:
从图中咱们能够看出的 Stub
和 Mock
都是实现同一个服务的接口,它们都是通过代理对象来调用近程裸露的服务,而 Mock
紧紧是在调用失败时会触发。
Tips:
Mock
是Stub
的一个子集,便于服务提供方在客户端执行容错逻辑,因常常须要在呈现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
简介中咱们能够晓得他们都是基于对调用代理对象的包装,也就是调用代理对象后面咱们能够做一些自定的操作。上面咱们简略的介绍一些工作中的应用场景:
- 前置校验器:在咱们工作中常常在调用操作前都会做一些校验,如果满足校验条件能力执行前面的逻辑。咱们能够利用
Stub
的形式在调用代理对象前做一些校验工作。 - 日志打印:咱们能够利用
Stub
在调用对象后面做一些通用的日志打印进去。 - 性能监控:利用
Stub
调用代理对象前后操作的工夫来计算服务调用的工夫。 - 服务降级:咱们能够利用
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"
示意开启本地存根模式。上面咱们别离配置 Stub
和 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);
}
}
这里的 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
的实现也是相似。后面的简介中咱们介绍了对于 Stub
和Mock
都是生产端对代理对象的包装,所有咱们能够大胆猜想对 Stub
和Mock
的包装过程就在 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);
}
}
下面的代码加载 XxxLocal
或XxxStub
的 Class
,校验办法 `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
(例如:XxxLocal
或XxxStub
)是否实现 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;
}
从下面的代码中能够看出近程调用代理对象传递给 XxxStub
或XxxLocal
模式的实例构造函数,并且把 XxxStub
或XxxLocal
返回给 ReferenceConfig.ref
,这样咱们应用 Dubbo 服务是获取的就是XxxStub
或XxxLocal
的实例对象。
6. 小结
在本大节中咱们次要学习了 Dubbo 中 Stub
和 Mock
以及两种模式的应用形式。同时也剖析了 Stub
和 Mock
实现的原理,其本质上是通过包装对援用代理对象的调用,其中 Mock
是 Stub
实现的一个子集针对调用抛出 RpcException
异样时被调用。
本节课程的重点如下:
- 了解 Dubbo
Stub
和Mock
- 理解了两种模式应用形式
- 理解
Stub
和Mock
实现原理 - 理解
Stub
和Mock
应用场景
作者
集体从事金融行业,就任过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就任于某银行负责对立领取零碎建设。本身对金融行业有强烈的喜好。同时也实际大数据、数据存储、自动化集成和部署、散布式微服务、响应式编程、人工智能等畛域。同时也热衷于技术分享创建公众号和博客站点对常识体系进行分享。关注公众号:青年 IT 男 获取最新技术文章推送!
博客地址: http://youngitman.tech
微信公众号: