SpringCloud Alibaba系列——9Dubbo的Mock原理

导读:本篇文章讲解 SpringCloud Alibaba系列——9Dubbo的Mock原理,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

学习目标

  1. Dubbo的mock方式有哪几种

  2. Dubbo的mock原理

第1章 Mock原理分析

SpringCloud Alibaba系列——9Dubbo的Mock原理

1.1 功能描述

在前面的高级用法中我们讲过mock的使用方式以及作用,在这里不再赘述,这里针对mock的集中配置形式来分析一下mock的实现原理。

1.2 mock = “force:return eclipse2019”

1.2.1 使用场景

这种一般不会出现在代码配置中,一般都是在服务治理的时候进行配置的,如果指定后端接口有问题了,可以在dubbo-admin中配置该接口对应的某方法进行强制降级。

1.2.2 源码分析

有关mock的实现逻辑全部都在MockClusterInvoker中,当我们用代理对象调用的时候,代码会走到代理的advice类,也就是InvokerInvocationHandler中,然后走到MigrationInvoker,然后走到了MockClusterInvoker当中,调到了其invoke方法,代码如下:

@Override
public Result invoke(Invocation invocation) throws RpcException {
    Result result = null;
    //获取url中的mock参数
    String value = getUrl().getMethodParameter(invocation.getMethodName(), MOCK_KEY,
                                               Boolean.FALSE.toString()).trim();
    if (value.length() == 0 || "false".equalsIgnoreCase(value)) {
        //no mock
        //如果没mock则直接走后面调用逻辑
        result = this.invoker.invoke(invocation);
    } else if (value.startsWith("force")) {
        //如果是force开头
        if (logger.isWarnEnabled()) {
            logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled, url : " + getUrl());
        }
        //强制降级,调用mock的实现类逻辑
        //force:direct mock
        result = doMockInvoke(invocation, null);
    } else {
        //fail-mock
        try {
            result = this.invoker.invoke(invocation);
            //fix:#4585
            if(result.getException() != null && result.getException() instanceof RpcException){
                RpcException rpcException= (RpcException)result.getException();
                if(rpcException.isBiz()){
                    throw  rpcException;
                }else {
                    result = doMockInvoke(invocation, rpcException);
                }
            }
        } catch (RpcException e) {
            if (e.isBiz()) {
                throw e;
            }
            if (logger.isWarnEnabled()) {
                logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + getUrl(), e);
            }
            result = doMockInvoke(invocation, e);
        }
    }
    return result;
}

从上述代码我们可以看到,MockClusterInvoker当中就是走了三套逻辑:

1、没有配置mock的情况

2、mock=”force:”的情况

3、配置了mock的其他情况

我们都知道如果配置了force:就代表要进行强制降级,就不会走后端的rpc调用了,所以这里是直接调用到了

result = doMockInvoke(invocation, null);
private Result doMockInvoke(Invocation invocation, RpcException e) {
    Result result = null;
    Invoker<T> minvoker;
    //选择一个MockInvoker的实例,这里是选不到的
    List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
    if (CollectionUtils.isEmpty(mockInvokers)) {
        //所以代码会走这里,创建一个MockInvoker对象
        minvoker = (Invoker<T>) new MockInvoker(getUrl(), directory.getInterface());
    } else {
        minvoker = mockInvokers.get(0);
    }
    try {
        //调用mock的实现类方法
        result = minvoker.invoke(invocation);
    } catch (RpcException me) {
        if (me.isBiz()) {
            result = AsyncRpcResult.newDefaultAsyncResult(me.getCause(), invocation);
        } else {
            throw new RpcException(me.getCode(), getMockExceptionMessage(e, me),
                                   me.getCause());
        }
    } catch (Throwable me) {
        throw new RpcException(getMockExceptionMessage(e, me), me.getCause());
    }
    return result;
}

我们再看看mockInvoker的invoke方法

@Override
public Result invoke(Invocation invocation) throws RpcException {
    if (invocation instanceof RpcInvocation) {
        ((RpcInvocation) invocation).setInvoker(this);
    }
    String mock = null;
    if (getUrl().hasMethodParameter(invocation.getMethodName())) {
        mock = getUrl().getParameter(invocation.getMethodName() + "." + MOCK_KEY);
    }
    if (StringUtils.isBlank(mock)) {
        //获取配置的mock的属性值
        mock = getUrl().getParameter(MOCK_KEY);
    }
    if (StringUtils.isBlank(mock)) {
        throw new RpcException(new IllegalAccessException("mock can not be null. url :" +
                                                          url));
    }
    //把force:前缀去掉,获取后面的值
    mock = normalizeMock(URL.decode(mock));
    //如果是return开头
    if (mock.startsWith(RETURN_PREFIX)) {
        //获取return后面的值
        mock = mock.substring(RETURN_PREFIX.length()).trim();
        try {
            //获取返回值类型
            Type[] returnTypes = RpcUtils.getReturnTypes(invocation);
            //把return后面的值包装成返回值类型
            Object value = parseMockValue(mock, returnTypes);
            //注解把结果返回没走后端rpc调用
            return AsyncRpcResult.newDefaultAsyncResult(value, invocation);
        } catch (Exception ew) {
            throw new RpcException("mock return invoke error. method :" +
                                   invocation.getMethodName()
                                   + ", mock:" + mock + ", url: " + url, ew);
        }
        //如果是throw
    } else if (mock.startsWith(THROW_PREFIX)) {
        mock = mock.substring(THROW_PREFIX.length()).trim();
        if (StringUtils.isBlank(mock)) {
            throw new RpcException("mocked exception for service degradation.");
        } else { // user customized class
            //获取异常实例
            Throwable t = getThrowable(mock);
            //直接往上抛异常
            throw new RpcException(RpcException.BIZ_EXCEPTION, t);
        }
    } else { //impl mock
        //mock实现类的方式
        try {
            Invoker<T> invoker = getInvoker(mock);
            //调用mock实例
            return invoker.invoke(invocation);
        } catch (Throwable t) {
            throw new RpcException("Failed to create mock implementation class " + mock,t);
        }
    }
}

我们看一下normalizeMock方法

public static String normalizeMock(String mock) {
    if (mock == null) {
        return mock;
    }
    mock = mock.trim();
    if (mock.length() == 0) {
        return mock;
    }
    //如果是只有一个return 则加上一个return null
    if (RETURN_KEY.equalsIgnoreCase(mock)) {
        return RETURN_PREFIX + "null";
    }
    if (ConfigUtils.isDefault(mock) || "fail".equalsIgnoreCase(mock) ||
        "force".equalsIgnoreCase(mock)) {
        return "default";
    }
    if (mock.startsWith(FAIL_PREFIX)) {
        mock = mock.substring(FAIL_PREFIX.length()).trim();
    }
    //把force:去掉
    if (mock.startsWith(FORCE_PREFIX)) {
        mock = mock.substring(FORCE_PREFIX.length()).trim();
    }
    if (mock.startsWith(RETURN_PREFIX) || mock.startsWith(THROW_PREFIX)) {
        mock = mock.replace('`', '"');
    }
    return mock;
}

我们获取到mock的返回值内容后需要把该返回值包装成方法返回值类型,所以这里必须要有一个返回值类型的包装,我们看一下parseMockValue方法:

public static Object parseMockValue(String mock, Type[] returnTypes) throws Exception {
    Object value = null;
    if ("empty".equals(mock)) {
        value = ReflectUtils.getEmptyObject(returnTypes != null && returnTypes.length > 0 ?
                                            (Class<?>) returnTypes[0] : null);
    } else if ("null".equals(mock)) {
        value = null;
    } else if ("true".equals(mock)) {
        value = true;
    } else if ("false".equals(mock)) {
        value = false;
    } else if (mock.length() >= 2 && (mock.startsWith("\"") && mock.endsWith("\"")
                                      || mock.startsWith("\'") && mock.endsWith("\'"))) {
        value = mock.subSequence(1, mock.length() - 1);
    } else if (returnTypes != null && returnTypes.length > 0 && returnTypes[0] ==
               String.class) {
        value = mock;
    } else if (StringUtils.isNumeric(mock, false)) {
        value = JSON.parse(mock);
    } else if (mock.startsWith("{")) {
        value = JSON.parseObject(mock, Map.class);
    } else if (mock.startsWith("[")) {
        value = JSON.parseObject(mock, List.class);
    } else {
        value = mock;
    }
    if (ArrayUtils.isNotEmpty(returnTypes)) {
        value = PojoUtils.realize(value, (Class<?>) returnTypes[0], returnTypes.length > 1
                                  ? returnTypes[1] : null);
    }
    return value;
}

从上面的逻辑来看,如果是force:return 则会不走rpc直接返回一个结果,然后把这个结果包装成方法的返回值类型。

1.3 mock = “true”

1.3.1 使用场景

如果配置的是mock=”true”就是一种约定俗成的方式,那么这种方式就代表会走远程调用,然后远程调用如果出现了RpcException的时候就会掉到降级逻辑,这个降级逻辑的定义必须满足两点:

1、类名必须是接口名+”Mock”

2、类必须定义在接口的同包名下

配置如下:

@DubboReference(check = false,mock = "true")

1.3.2 源码分析

在MockClusterInvoker中就会走这个逻辑:

//fail-mock
try {
    //后端接口调用
    result = this.invoker.invoke(invocation);
    //fix:#4585
    if(result.getException() != null && result.getException() instanceof RpcException){
        RpcException rpcException= (RpcException)result.getException();
        if(rpcException.isBiz()){
            throw  rpcException;
        }else {
            result = doMockInvoke(invocation, rpcException);
        }
    }
} catch (RpcException e) {
    if (e.isBiz()) {
        throw e;
    }
    if (logger.isWarnEnabled()) {
        logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url: " + getUrl(), e);
    }
    result = doMockInvoke(invocation, e);
}

从上面逻辑来看,会先进行invoke调用,如果后端调用有问题则会被catch捕获然后走doMockInvoke逻辑进行降级处理。我们来看看doMockInvoke中的逻辑;在MockInvoker中就会走到实现类mock逻辑;

//mock实现类的方式
try {
    //获取Invoker对象
    Invoker<T> invoker = getInvoker(mock);
    //调用mock实例
    return invoker.invoke(invocation);
} catch (Throwable t) {
    throw new RpcException("Failed to create mock implementation class " + mock, t);
}

重点看一下getInvoker方法:

private Invoker<T> getInvoker(String mockService) {
    Invoker<T> invoker = (Invoker<T>) MOCK_MAP.get(mockService);
    if (invoker != null) {
        return invoker;
    }
    //接口类型
    Class<T> serviceType = (Class<T>) ReflectUtils.forName(url.getServiceInterface());
    //核心代码 ,获取Mock实现类实例
    T mockObject = (T) getMockObject(mockService, serviceType);
    //获取用于调用Mock实例类的invoker对象
    invoker = PROXY_FACTORY.getInvoker(mockObject, serviceType, url);
    if (MOCK_MAP.size() < 10000) {
        MOCK_MAP.put(mockService, invoker);
    }
    return invoker;
}

getMockObject逻辑

public static Object getMockObject(String mockService, Class serviceType) {
    //如果配置的是true或者default ,则走默认mock实现类
    boolean isDefault = ConfigUtils.isDefault(mockService);
    if (isDefault) {
        //默认实现类就是 : 接口完整限定名+Mock
        mockService = serviceType.getName() + "Mock";
    }
    Class<?> mockClass;
    try {
        // 如果 mockService不是配置的true,如果是配置的类的完整限定名
        mockClass = ReflectUtils.forName(mockService);
    } catch (Exception e) {
        if (!isDefault) {// does not check Spring bean if it is default config.
            ExtensionFactory extensionFactory =

                ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
            Object obj = extensionFactory.getExtension(serviceType, mockService);
            if (obj != null) {
                return obj;
            }
        }
        throw new IllegalStateException("Did not find mock class or instance "
                                        + mockService
                                        + ", please check if there's mock class or instance implementing interface
                                        "
                                        + serviceType.getName(), e);
    }
    if (mockClass == null || !serviceType.isAssignableFrom(mockClass)) {
        throw new IllegalStateException("The mock class " + mockClass.getName() + 
                                        " not implement interface " + serviceType.getName());
    }
    try {
        return mockClass.newInstance();
    } catch (InstantiationException e) {
        throw new IllegalStateException("No default constructor from mock class " +
                                        mockClass.getName(), e);
    } catch (IllegalAccessException e) {
        throw new IllegalStateException(e);
    }
}

从上面的代码我们可以看到,获取到实现类的实例的方式就是两种:

1、如果配置的是true,实现类就是:接口名+”Mock”

2、直接配置的类的完整限定名

在看看获取invoker的代码:

invoker = PROXY_FACTORY.getInvoker(mockObject, serviceType, url);

这个代码最终会走到JavassistProxyFactory类的getInvoker方法中,spi的方式获取实例,这里就不再赘述

@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0
                                               ? proxy.getClass() : type);
    return new AbstractProxyInvoker<T>(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName,
                                  Class<?>[] parameterTypes,
                                  Object[] arguments) throws Throwable {
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

这里是先生成了一个代理类wrapper,这个代理类中有一个invokeMethod方法,只要你传给他要掉的类的实例,方法名称,参数类型和参数列表就可以完成方法的调用,然后AbstractProxyInvoker持有了wrapper的引用,我们如果需要调用一个类的方法,只有用invoker对象调用invoke方法就可以了。这里为什么能调到MockServiceMock就不再赘述了。

1.4 mock = “xx.LocalMockService”

1.4.1 使用场景

同mock=“true”

配置:

@DubboReference(check = false,mock = "cn.enjoy.mock.LocalMockService")

1.4.2 源码分析

同mock=“true”

1.5 mock = “throw xx”

1.5.1 使用场景

当调用接口出现问题后需要直接抛出异常的情况可以实现它

配置如下:

@DubboReference(check = false,mock = “throw java.lang.RuntimeException”)

1.5.2 源码分析

else if (mock.startsWith(THROW_PREFIX)) {
    mock = mock.substring(THROW_PREFIX.length()).trim();
    if (StringUtils.isBlank(mock)) {
        throw new RpcException("mocked exception for service degradation.");
    } else { // user customized class
        //获取异常实例
        Throwable t = getThrowable(mock);
        //直接往上抛异常
        throw new RpcException(RpcException.BIZ_EXCEPTION, t);
    }
}
public static Throwable getThrowable(String throwstr) {
    Throwable throwable = THROWABLE_MAP.get(throwstr);
    if (throwable != null) {
        return throwable;
    }
    try {
        Throwable t;
        //反射异常类
        Class<?> bizException = ReflectUtils.forName(throwstr);
        Constructor<?> constructor;
        constructor = ReflectUtils.findConstructor(bizException, String.class);
        //异常实例化
        t = (Throwable) constructor.newInstance(new Object[]{"mocked exception for service degradation."});
        if (THROWABLE_MAP.size() < 1000) {
            THROWABLE_MAP.put(throwstr, t);
        }
        return t;
    } catch (Exception e) {
        throw new RpcException("mock throw error :" + throwstr + " argument error.", e);
    }
}

从源码分析来看,这里是注解获取到了配置的异常类的字符串,然后反射实例化,然后把这个异常直接往上抛了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/76700.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!