- Fix NPE bug in consumer filter (when non-biz error occurred) - Improve default fallback in Dubbo 2.7.x adapter: convert the BlockException to a simple RuntimeException (with necessary message) - Polish code and comments Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -1,4 +1,4 @@ | |||
# Sentinel Apache Dubbo Adapter | |||
# Sentinel Apache Dubbo Adapter (for 2.7.x+) | |||
> Note: 中文文档请见[此处](https://github.com/alibaba/Sentinel/wiki/主流框架的适配#dubbo)。 | |||
@@ -21,7 +21,7 @@ To use Sentinel Dubbo Adapter, you can simply add the following dependency to yo | |||
The Sentinel filters are **enabled by default**. Once you add the dependency, | |||
the Dubbo services and methods will become protected resources in Sentinel, | |||
which can leverage Sentinel's flow control and guard ability when rules are configured. | |||
Demos can be found in [sentinel-demo-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-dubbo). | |||
Demos can be found in [sentinel-demo-apache-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-apache-dubbo). | |||
If you don't want the filters enabled, you can manually disable them. For example: | |||
@@ -37,8 +37,8 @@ For more details of Dubbo filter, see [here](http://dubbo.apache.org/en-us/docs/ | |||
The resource for Dubbo services has two granularities: service interface and service method. | |||
- Service interface:resourceName format is `interfaceName`,e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService` | |||
- Service method:resourceName format is `interfaceName:methodSignature`,e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)` | |||
- Service interface: resourceName format is `interfaceName`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService` | |||
- Service method: resourceName format is `interfaceName:methodSignature`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)` | |||
## Flow control based on caller | |||
@@ -21,11 +21,10 @@ import org.apache.dubbo.rpc.Invocation; | |||
import org.apache.dubbo.rpc.Invoker; | |||
/** | |||
* Base Class of the {@link SentinelDubboProviderFilter} and {@link SentinelDubboConsumerFilter}. | |||
* Base class of the {@link SentinelDubboProviderFilter} and {@link SentinelDubboConsumerFilter}. | |||
* | |||
* @author Zechao Zheng | |||
*/ | |||
public abstract class BaseSentinelDubboFilter implements Filter { | |||
@@ -19,11 +19,13 @@ import com.alibaba.csp.sentinel.*; | |||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
import org.apache.dubbo.common.extension.Activate; | |||
import org.apache.dubbo.rpc.*; | |||
import org.apache.dubbo.rpc.support.RpcUtils; | |||
import java.util.LinkedList; | |||
import java.util.Optional; | |||
import java.util.function.BiConsumer; | |||
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER; | |||
@@ -38,6 +40,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER; | |||
* | |||
* @author Carpenter Lee | |||
* @author Eric Zhao | |||
* @author Lin Liang | |||
*/ | |||
@Activate(group = CONSUMER) | |||
public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter { | |||
@@ -64,7 +67,6 @@ public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter { | |||
} else { | |||
return asyncInvoke(invoker, invocation); | |||
} | |||
} | |||
private Result syncInvoke(Invoker<?> invoker, Invocation invocation) { | |||
@@ -75,7 +77,8 @@ public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter { | |||
String methodResourceName = getMethodName(invoker, invocation, prefix); | |||
try { | |||
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); | |||
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, invocation.getArguments()); | |||
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, | |||
invocation.getArguments()); | |||
Result result = invoker.invoke(invocation); | |||
if (result.hasException()) { | |||
Tracer.traceEntry(result.getException(), interfaceEntry); | |||
@@ -98,24 +101,27 @@ public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter { | |||
} | |||
} | |||
private Result asyncInvoke(Invoker<?> invoker, Invocation invocation) { | |||
LinkedList<EntryHolder> queue = new LinkedList<>(); | |||
String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); | |||
String interfaceResourceName = getInterfaceName(invoker, prefix); | |||
String methodResourceName = getMethodName(invoker, invocation, prefix); | |||
try { | |||
queue.push(new EntryHolder(SphU.asyncEntry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT), null)); | |||
queue.push(new EntryHolder(SphU.asyncEntry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, 1, invocation.getArguments()), invocation.getArguments())); | |||
queue.push(new EntryHolder( | |||
SphU.asyncEntry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT), null)); | |||
queue.push(new EntryHolder( | |||
SphU.asyncEntry(methodResourceName, ResourceTypeConstants.COMMON_RPC, | |||
EntryType.OUT, 1, invocation.getArguments()), invocation.getArguments())); | |||
Result result = invoker.invoke(invocation); | |||
result.whenCompleteWithContext(new BiConsumer<Result, Throwable>() { | |||
@Override | |||
public void accept(Result result, Throwable throwable) { | |||
while (!queue.isEmpty()) { | |||
EntryHolder holder = queue.pop(); | |||
Tracer.traceEntry(result.getException(), holder.entry); | |||
exitEntry(holder); | |||
} | |||
result.whenCompleteWithContext((r, throwable) -> { | |||
Throwable error = throwable; | |||
if (error == null) { | |||
error = Optional.ofNullable(r).map(Result::getException).orElse(null); | |||
} | |||
while (!queue.isEmpty()) { | |||
EntryHolder holder = queue.pop(); | |||
Tracer.traceEntry(error, holder.entry); | |||
exitEntry(holder); | |||
} | |||
}); | |||
return result; | |||
@@ -127,10 +133,9 @@ public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter { | |||
} | |||
} | |||
class EntryHolder { | |||
static class EntryHolder { | |||
final private Entry entry; | |||
final private Object[] params; | |||
public EntryHolder(Entry entry, Object[] params) { | |||
@@ -20,6 +20,7 @@ import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; | |||
import com.alibaba.csp.sentinel.context.ContextUtil; | |||
import com.alibaba.csp.sentinel.log.RecordLog; | |||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
import org.apache.dubbo.common.extension.Activate; | |||
import org.apache.dubbo.rpc.Invocation; | |||
import org.apache.dubbo.rpc.Invoker; | |||
@@ -74,7 +75,8 @@ public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter { | |||
// at entrance of invocation chain only (for inbound traffic). | |||
ContextUtil.enter(methodResourceName, origin); | |||
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); | |||
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, invocation.getArguments()); | |||
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, | |||
invocation.getArguments()); | |||
Result result = invoker.invoke(invocation); | |||
if (result.hasException()) { | |||
Tracer.traceEntry(result.getException(), interfaceEntry); | |||
@@ -98,6 +100,5 @@ public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter { | |||
} | |||
} | |||
} | |||
@@ -43,13 +43,11 @@ public final class DubboAdapterGlobalConfig { | |||
private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:"; | |||
public static final String DUBBO_INTERFACE_GROUP_VERSION_ENABLED = "csp.sentinel.dubbo.interface.group.version.enabled"; | |||
public static final String TRACE_BIZ_EXCEPTION_ENABLED = "csp.sentinel.dubbo.trace.biz.exception.enabled"; | |||
private static volatile DubboFallback consumerFallback = new DefaultDubboFallback(); | |||
private static volatile DubboFallback providerFallback = new DefaultDubboFallback(); | |||
private static volatile DubboOriginParser originParser = new DefaultDubboOriginParser(); | |||
public static boolean isUsePrefix() { | |||
return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_RES_NAME_WITH_PREFIX_KEY)); | |||
} | |||
@@ -74,16 +72,6 @@ public final class DubboAdapterGlobalConfig { | |||
return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED)); | |||
} | |||
public static Boolean getDubboBizExceptionTraceEnabled() { | |||
String traceBizExceptionEnabled = SentinelConfig.getConfig(TRACE_BIZ_EXCEPTION_ENABLED); | |||
if (StringUtil.isNotBlank(traceBizExceptionEnabled)) { | |||
return TRUE_STR.equalsIgnoreCase(traceBizExceptionEnabled); | |||
} | |||
return true; | |||
} | |||
public static DubboFallback getConsumerFallback() { | |||
return consumerFallback; | |||
} | |||
@@ -16,7 +16,7 @@ | |||
package com.alibaba.csp.sentinel.adapter.dubbo.fallback; | |||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; | |||
import org.apache.dubbo.rpc.AsyncRpcResult; | |||
import org.apache.dubbo.rpc.Invocation; | |||
import org.apache.dubbo.rpc.Invoker; | |||
@@ -30,6 +30,6 @@ public class DefaultDubboFallback implements DubboFallback { | |||
@Override | |||
public Result handle(Invoker<?> invoker, Invocation invocation, BlockException ex) { | |||
// Just wrap the exception. | |||
return AsyncRpcResult.newDefaultAsyncResult(null, new SentinelRpcException(ex), invocation); | |||
return AsyncRpcResult.newDefaultAsyncResult(ex.toRuntimeException(), invocation); | |||
} | |||
} |
@@ -16,18 +16,14 @@ | |||
package com.alibaba.csp.sentinel.adapter.dubbo.fallback; | |||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; | |||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||
/** | |||
* <p>Global fallback registry for Dubbo.</p> | |||
* | |||
* <p> | |||
* Note: Circuit breaking is mainly designed for consumer. The provider should not | |||
* give fallback result in most circumstances. | |||
* </p> | |||
* | |||
* @author Eric Zhao | |||
* @deprecated use {@link DubboAdapterGlobalConfig} instead since 1.8.0. | |||
*/ | |||
@Deprecated | |||
public final class DubboFallbackRegistry { | |||
public static DubboFallback getConsumerFallback() { | |||
@@ -35,7 +31,6 @@ public final class DubboFallbackRegistry { | |||
} | |||
public static void setConsumerFallback(DubboFallback consumerFallback) { | |||
AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null"); | |||
DubboAdapterGlobalConfig.setConsumerFallback(consumerFallback); | |||
} | |||
@@ -44,7 +39,6 @@ public final class DubboFallbackRegistry { | |||
} | |||
public static void setProviderFallback(DubboFallback providerFallback) { | |||
AssertUtil.notNull(providerFallback, "providerFallback cannot be null"); | |||
DubboAdapterGlobalConfig.setProviderFallback(providerFallback); | |||
} | |||
@@ -19,6 +19,7 @@ import com.alibaba.csp.sentinel.BaseTest; | |||
import com.alibaba.csp.sentinel.DubboTestUtil; | |||
import com.alibaba.csp.sentinel.Entry; | |||
import com.alibaba.csp.sentinel.EntryType; | |||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; | |||
import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback; | |||
import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry; | |||
import com.alibaba.csp.sentinel.context.Context; | |||
@@ -34,6 +35,7 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; | |||
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; | |||
import org.apache.dubbo.rpc.*; | |||
import org.apache.dubbo.rpc.support.RpcUtils; | |||
import org.junit.After; | |||
@@ -53,8 +55,7 @@ import static org.mockito.Mockito.*; | |||
*/ | |||
public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
private SentinelDubboConsumerFilter consumerFilter = new SentinelDubboConsumerFilter(); | |||
private final SentinelDubboConsumerFilter consumerFilter = new SentinelDubboConsumerFilter(); | |||
@Before | |||
public void setUp() { | |||
@@ -67,7 +68,6 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
cleanUpAll(); | |||
} | |||
@Test | |||
public void testInterfaceLevelFollowControlAsync() throws InterruptedException { | |||
@@ -105,7 +105,6 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
verifyInvocationStructureForCallFinish(invoker, invocation); | |||
assertEquals("normal", result.getValue()); | |||
// inc the clusterNode's exception to trigger the fallback | |||
for (int i = 0; i < 5; i++) { | |||
invokeDubboRpc(true, invoker, invocation); | |||
@@ -153,7 +152,6 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
assertNull(context); | |||
} | |||
@Test | |||
public void testMethodFlowControlAsync() { | |||
@@ -175,10 +173,8 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
assertEquals("fallback", fallback.getValue()); | |||
verifyInvocationStructureForCallFinish(invoker, invocation); | |||
} | |||
@Test | |||
public void testInvokeAsync() { | |||
@@ -190,7 +186,7 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
when(result.hasException()).thenReturn(false); | |||
when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> { | |||
verifyInvocationStructureForAsyncCall(invoker, invocation); | |||
return result; | |||
return result; | |||
}); | |||
consumerFilter.invoke(invoker, invocation); | |||
verify(invoker).invoke(invocation); | |||
@@ -220,7 +216,6 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
assertNull(context); | |||
} | |||
/** | |||
* Simply verify invocation structure in memory: | |||
* EntranceNode(defaultContextName) | |||
@@ -231,7 +226,8 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
Context context = ContextUtil.getContext(); | |||
assertNotNull(context); | |||
// As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context | |||
// In actual project, a consumer is usually also a provider, the context will be created by SentinelDubboProviderFilter | |||
// In actual project, a consumer is usually also a provider, the context will be created by | |||
//SentinelDubboProviderFilter | |||
// If consumer is on the top of Dubbo RPC invocation chain, use default context | |||
String resourceName = consumerFilter.getMethodName(invoker, invocation, null); | |||
assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName()); | |||
@@ -269,7 +265,8 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
// Verify clusterNode | |||
ClusterNode methodClusterNode = methodNode.getClusterNode(); | |||
ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); | |||
assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode | |||
assertNotSame(methodClusterNode, | |||
interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode | |||
// As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode | |||
Map<String, StatisticNode> methodOriginCountMap = methodClusterNode.getOriginCountMap(); | |||
@@ -284,7 +281,8 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
assertNotNull(context); | |||
// As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context | |||
// In actual project, a consumer is usually also a provider, the context will be created by SentinelDubboProviderFilter | |||
// In actual project, a consumer is usually also a provider, the context will be created by | |||
//SentinelDubboProviderFilter | |||
// If consumer is on the top of Dubbo RPC invocation chain, use default context | |||
String resourceName = consumerFilter.getMethodName(invoker, invocation, null); | |||
assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName()); | |||
@@ -319,7 +317,8 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
// Verify clusterNode | |||
ClusterNode methodClusterNode = methodNode.getClusterNode(); | |||
ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); | |||
assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode | |||
assertNotSame(methodClusterNode, | |||
interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode | |||
// As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode | |||
Map<String, StatisticNode> methodOriginCountMap = methodClusterNode.getOriginCountMap(); | |||
@@ -329,7 +328,6 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
assertEquals(0, interfaceOriginCountMap.size()); | |||
} | |||
private void verifyInvocationStructureForCallFinish(Invoker invoker, Invocation invocation) { | |||
Context context = ContextUtil.getContext(); | |||
assertNull(context); | |||
@@ -338,7 +336,6 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
assertNull(entries); | |||
} | |||
private DefaultNode getNode(String resourceName, DefaultNode root) { | |||
Queue<DefaultNode> queue = new LinkedList<>(); | |||
@@ -355,7 +352,6 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
return null; | |||
} | |||
private void initFlowRule(String resource) { | |||
FlowRule flowRule = new FlowRule(resource); | |||
flowRule.setCount(1); | |||
@@ -367,24 +363,18 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
private void initDegradeRule(String resource) { | |||
DegradeRule degradeRule = new DegradeRule(resource) | |||
.setCount(0.5) | |||
.setGrade(DEGRADE_GRADE_EXCEPTION_RATIO); | |||
.setCount(0.5) | |||
.setGrade(DEGRADE_GRADE_EXCEPTION_RATIO); | |||
List<DegradeRule> degradeRules = new ArrayList<>(); | |||
degradeRules.add(degradeRule); | |||
degradeRule.setTimeWindow(1); | |||
DegradeRuleManager.loadRules(degradeRules); | |||
} | |||
private void initFallback() { | |||
DubboFallbackRegistry.setConsumerFallback(new DubboFallback() { | |||
@Override | |||
public Result handle(Invoker<?> invoker, Invocation invocation, BlockException ex) { | |||
boolean async = RpcUtils.isAsync(invoker.getUrl(), invocation); | |||
Result fallbackResult = null; | |||
fallbackResult = AsyncRpcResult.newDefaultAsyncResult("fallback", invocation); | |||
return fallbackResult; | |||
} | |||
DubboAdapterGlobalConfig.setConsumerFallback((invoker, invocation, ex) -> { | |||
// boolean async = RpcUtils.isAsync(invoker.getUrl(), invocation); | |||
return AsyncRpcResult.newDefaultAsyncResult("fallback", invocation); | |||
}); | |||
} | |||
@@ -394,13 +384,11 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { | |||
if (InvokeMode.SYNC == invokeMode) { | |||
result = exception ? new AppResponse(new Exception("error")) : new AppResponse("normal"); | |||
} else { | |||
result = exception ? AsyncRpcResult.newDefaultAsyncResult(new Exception("error"), invocation) : AsyncRpcResult.newDefaultAsyncResult("normal", invocation); | |||
result = exception ? AsyncRpcResult.newDefaultAsyncResult(new Exception("error"), invocation) | |||
: AsyncRpcResult.newDefaultAsyncResult("normal", invocation); | |||
} | |||
when(invoker.invoke(invocation)).thenReturn(result); | |||
return consumerFilter.invoke(invoker, invocation); | |||
} | |||
} |
@@ -17,8 +17,8 @@ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; | |||
import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; | |||
import com.alibaba.csp.sentinel.slots.block.BlockException; | |||
import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowException; | |||
import org.apache.dubbo.rpc.AsyncRpcResult; | |||
import org.apache.dubbo.rpc.Result; | |||
import org.junit.After; | |||
@@ -33,12 +33,12 @@ public class DubboFallbackRegistryTest { | |||
@Before | |||
public void setUp() { | |||
DubboFallbackRegistry.setConsumerFallback(new DefaultDubboFallback()); | |||
DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); | |||
} | |||
@After | |||
public void tearDown() { | |||
DubboFallbackRegistry.setConsumerFallback(new DefaultDubboFallback()); | |||
DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); | |||
} | |||
@Test | |||
@@ -46,15 +46,17 @@ public class DubboFallbackRegistryTest { | |||
// Test for default fallback. | |||
BlockException ex = new FlowException("xxx"); | |||
Result result = new DefaultDubboFallback().handle(null, null, ex); | |||
Assert.assertTrue("The invocation should not fail",result.hasException()); | |||
Assert.assertEquals(SentinelRpcException.class, result.getException().getClass()); | |||
Assert.assertTrue("The result should carry exception", result.hasException()); | |||
Assert.assertTrue(BlockException.isBlockException(result.getException())); | |||
Assert.assertTrue(result.getException().getMessage().contains(ex.getClass().getSimpleName())); | |||
} | |||
@Test | |||
public void testCustomFallback() { | |||
BlockException ex = new FlowException("xxx"); | |||
DubboAdapterGlobalConfig.setConsumerFallback( | |||
(invoker, invocation, e) -> AsyncRpcResult.newDefaultAsyncResult("Error: " + e.getClass().getName(), invocation)); | |||
(invoker, invocation, e) -> AsyncRpcResult | |||
.newDefaultAsyncResult("Error: " + e.getClass().getName(), invocation)); | |||
Result result = DubboAdapterGlobalConfig.getConsumerFallback() | |||
.handle(null, null, ex); | |||
Assert.assertFalse("The invocation should not fail", result.hasException()); | |||