在開發(fā)自測,聯(lián)調(diào)過程中,經(jīng)常碰到一些下游服務(wù)調(diào)用不通的場景,這個時候我們?nèi)绾尾灰蕾囉谙掠蜗到y(tǒng),就業(yè)務(wù)系統(tǒng)獨立完成自測?
dubbo自身是支持mock服務(wù)的,在reference標(biāo)簽里,有一個參數(shù)mock,該參數(shù)有四個值,false,default,true,或者Mock類的類名。分別代表如下含義:
-
false,不調(diào)用mock服務(wù)。 -
true,當(dāng)服務(wù)調(diào)用失敗時,使用mock服務(wù)。 -
default,當(dāng)服務(wù)調(diào)用失敗時,使用mock服務(wù)。 -
force,強制使用Mock服務(wù)(不管服務(wù)能否調(diào)用成功)。(使用xml配置不生效,使用ReferenceConfigAPI可以生效)
使用方法:
將mock參數(shù)啟用,在<dubbo:reference>中添加參數(shù)項mock=true。
-
實現(xiàn)需要調(diào)用的服務(wù)接口
- 上游系統(tǒng)需要調(diào)用下游系統(tǒng),則下游需要提供jar包給上有系統(tǒng),該jar包只有接口,沒有實現(xiàn)。我們需要實現(xiàn)該接口,且命名必須是該接口名+Mock,例如原接口是
com.alibaba.dubbo.demo.DemoService,則實現(xiàn)類必須是com.alibaba.dubbo.demo.DemoServiceMock。
舉個例子:
- 上游系統(tǒng)需要調(diào)用下游系統(tǒng),則下游需要提供jar包給上有系統(tǒng),該jar包只有接口,沒有實現(xiàn)。我們需要實現(xiàn)該接口,且命名必須是該接口名+Mock,例如原接口是
<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();
}
}
}
當(dāng)我下游服務(wù)不啟動的時候,也就是沒有com.alibaba.dubbo.demo.DemoService的時候,運行該main函數(shù),執(zhí)行結(jié)果如下:
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)
當(dāng)我將mock="true"加載demoService上以后,執(zhí)行結(jié)果如下:
<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
可以看到結(jié)果,先去調(diào)用下游服務(wù),發(fā)現(xiàn)沒服務(wù),報錯,然后調(diào)用Mock服務(wù),返回hello world。
當(dāng)我將mock參數(shù)改為force的時候再執(zhí)行,拋異常了。
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)
當(dāng)dubbo服務(wù)在啟動獲取bean服務(wù)的時候,首先會檢查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的參數(shù)
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());
}
}
}
}
重點來了,當(dāng)我不使用xml配置時,直接使用referenceConfig獲取下游服務(wù),可以調(diào)用成功,返回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。。。。。
不關(guān)心這個了,重點是原理,mock是如何實現(xiàn)的。
再看看調(diào)用過程:
- 查看是否有Mock的參數(shù),沒有或false,則直接調(diào)用invoker。
- 若是以
force開頭,則強制走Mock - 其他場景,先調(diào)用下游,失敗了調(diào)用
Mock服務(wù)。
在調(diào)用Mock服務(wù)過程中,當(dāng)發(fā)現(xiàn)沒有Mock服務(wù)時,new一個MockInvoker,調(diào)用MockInvoker的服務(wù)。
public class MockClusterInvoker<T> implements Invoker<T> {
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
//判斷是否有Mock參數(shù)
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參數(shù)是否以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 {
//調(diào)用Mock服務(wù)。
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;
}
}
在調(diào)用過程中,先從緩存中獲取,獲取不到,調(diào)用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服務(wù)實例
T mockObject = (T) mockClass.newInstance();
invoker = proxyFactory.getInvoker(mockObject, (Class<T>) serviceType, url);
return invoker;
}
}
fyi